springboot+springsecurity+vue實現簡單的登陸認證
阿新 • • 發佈:2020-07-31
1、建立SecurityUser
類,需要實現UserDetails
介面
import lombok.Data; import lombok.EqualsAndHashCode; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.io.Serializable; import java.util.Collection; import java.util.Date; /** * * 封裝登陸使用者的資訊 */ @Data public class SecurityUser implements UserDetails { private String uid; private String username; private String password; private Integer sex; private String description; private Integer age; private String birthday; private Integer isDeleted; private Integer status;//1未禁用0禁用 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
2、編寫UserAuthenticationFilter
過濾器,這裡需要繼承UsernamePasswordAuthenticationFilter
原因:
通過檢視UsernamePasswordAuthenticationFilter
獲取使用者名稱和密碼的實現方法可以看到,預設只能獲取form表單提供的資料,無法獲得請求體中的資料。所以,要想獲得請求體中的資料,需要自定義過濾器。
這裡有兩種方式獲得使用者名稱和密碼
- 直接重寫
obtainPassword
和obtainUsername
- 檢視
attemptAuthentication
這個方法我們可以發現,使用者名稱和密碼是在這個方法裡面獲得並且使用的,因此我們可以直接重寫這個方法。(這裡使用的第二種方式)
import com.background.modules.security.bean.SecurityUser; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Slf4j public class UserAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Autowired private AuthenticationManager authenticationManager; @Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException { try { SecurityUser user = new ObjectMapper().readValue(req.getInputStream(), SecurityUser.class); return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword())); } catch (IOException e) { throw new RuntimeException(e.getMessage()); } } }
3、編寫UserDetailsServiceImpl
,並且實現UserDetailsService
這裡只需要實現loadUserByUsername
方法,驗證使用者是否存在、是否被禁用
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.background.modules.security.bean.SecurityUser;
import com.background.modules.user.bean.User;
import com.background.modules.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.getOne(new QueryWrapper<User>().eq("username", username));
if(user==null){
throw new BadCredentialsException("使用者不存在");
}
if(user.getStatus()==0){
throw new BadCredentialsException("使用者被禁用");
}
SecurityUser userInfo = new SecurityUser();
BeanUtils.copyProperties(user,userInfo);
return userInfo;
}
}
4、編寫UserLoginAuthenticationProvider
,繼承DaoAuthenticationProvider
通過繼承DaoAuthenticationProvider
,可以自定義使用者密碼驗證並檢視異常資訊。
具體效果
若不實現該類,丟擲的異常資訊會都變成Bad credentials
具體效果
import com.background.modules.security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class UserLoginAuthenticationProvider extends DaoAuthenticationProvider {
@Autowired
private UserDetailsServiceImpl detailsService;
@Autowired
private PasswordEncoder encoder;
/**
* 找到容器中的detailsService,並執行setUserDetailsService方法,完成賦值
*
* 必須要給UserDetailsService賦值,否則會出現UnsatisfiedDependencyException
*/
@Autowired
private void setDetailsService() {
setUserDetailsService(detailsService);
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
String presentedPassword = authentication.getCredentials().toString();
if (!encoder.matches(presentedPassword, userDetails.getPassword())) {
throw new BadCredentialsException(messages.getMessage("badCredentials", "使用者密碼錯誤"));
}
}
}
5、編寫主配置類SecurityConfig
,繼承自WebSecurityConfigurerAdapter
import com.alibaba.fastjson.JSON;
import com.background.modules.security.filter.UserAuthenticationFilter;
import com.background.modules.security.provider.UserLoginAuthenticationProvider;
import com.common.util.R;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* Created by X on 2020/7/22 16:30
*/
@Slf4j
@SpringBootConfiguration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserLoginAuthenticationProvider loginAuthenticationProvider;
/**
* 密碼加密
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 請求攔截、對映
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(auth->{
//開放swagger、登陸頁面的訪問許可權
auth.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/v2/**").permitAll()
.antMatchers("/api/**").permitAll()
.antMatchers("/background/login").permitAll()
.anyRequest().authenticated();
});
//啟用自定義的過濾器
http.addFilterAt(userAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.cors();//啟用跨域
http.csrf().disable();//關閉跨站攻擊
}
/**
* 使用者認證
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//使用自定義的Provider,進行資料校驗
auth.authenticationProvider(loginAuthenticationProvider);
}
/**
* 解決無法直接注入 AuthenticationManager
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception
{
return super.authenticationManager();
}
/**
* 自定義成功回撥、失敗回撥、登陸url地址等
*
* 可以在自定義UserAuthenticationFilter裡面直接重寫對應方法,
* 例 成功回撥:
* @Override
* public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) {
* super.setAuthenticationSuccessHandler(successHandler);
* }
* @return
* @throws Exception
*/
@Bean
public UserAuthenticationFilter userAuthenticationFilter() throws Exception {
UserAuthenticationFilter filter = new UserAuthenticationFilter();
//設定驗證成功後的回撥
filter.setAuthenticationSuccessHandler((request,response,authentication)->{
log.info("使用者認證成功");
//響應成功狀態碼必須為200
response.setStatus(HttpStatus.SC_OK);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("utf-8");
//將資料以json的形式返回給前臺
response.getWriter().print(JSON.toJSONString(R.ok()));
});
//設定驗證失敗後的回撥
filter.setAuthenticationFailureHandler((request, response, exception) ->{
log.info("使用者認證失敗----{}",exception.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("utf-8");
//將資料以json的形式返回給前臺
response.getWriter().print(JSON.toJSONString(R.error(exception.getMessage())));
});
//設定使用者發起登陸請求時的url
filter.setFilterProcessesUrl("/background/login");
filter.setAuthenticationManager(authenticationManager());
return filter;
}
}
若沒有實現UserLoginAuthenticationProvider
,這需要通過下面的方法實現資料校驗
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
6、vue開啟請求攜帶cookies
axios.defaults.withCredentials=true//開啟攜帶cookies