整体流程:
1、核心类websecurityAdapter(需要注意的我都注释了)
//注意这个注解一定要加
@EnableWebSecurity
public class WebSecuirityAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private LoginSuccessHandler successHandler;
@Autowired
private LoginFailHandler failHandler;
@Autowired
private MyUserDetailsService myUserDetailsService;
//将用户信息加载到security
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
.authorizeRequests();
registry.and()
// 表单登录方式
.formLogin()
// 登录请求url
.loginProcessingUrl("/user/login")
// .loginPage("https://www.baidu.com")
.permitAll()
.successHandler(successHandler)
.failureHandler(failHandler)
.and()
.authorizeRequests()
// 任何请求
.anyRequest()
// 需要身份认证
.authenticated()
.and()
// 注意这个要关闭,否则可能因为这里验证导致无法使用自定义token访问数据,关闭跨站请求防护
.csrf().disable()
.addFilter(new MyAuthenticationFilter(authenticationManager()))
// 前后端分离 不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
}
2、SecurityUser,可以继承用户自定义user,相当于给自定义user进行了封装,供框架使用
@Data
public class SecurityUser extends User implements UserDetails {
//此用户是否禁用,禁用0,可用1
private String status;
private Boolean enabled;
private Boolean credentialsNonExpired;
private Boolean accountNonExpired;
private Boolean accountNonLocked;
private Collection<? extends GrantedAuthority> authorities;
public SecurityUser(){};
public SecurityUser(User user){
this.setId(user.getId());
this.setNickname(user.getNickname());
this.setUsername(user.getUsername());
this.setPassword(user.getPassword());
this.setUsertype(user.getUsertype());
this.setStatus(user.getStatus());
this.setValidtime(user.getValidtime());
this.setTelephone(user.getTelephone());
this.setEmail(user.getEmail());
this.setUpdateby(user.getUpdateby());
this.setUpdatetime(user.getUpdatetime());
this.setRoles(user.getRoles());
status = user.getStatus();
}
//将role加载到Collection中,后续在加载到springsecurity
@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 status.equals("1");
}
}
3、MyUserDetailsService
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
IUserService iUserService;
//用户前台表单输入的用户名会传到这里,然后用这个名去数据库查询是否有这个用户,有的话加载到security内存中,然后再验证前台传来的密码是不是正确的
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = null;
if(StringUtils.isNotBlank(username)){
user = iUserService.findByName(username);
}else {
throw new SystemException("请填写用户名");
}
if(null!=user){
return new SecurityUser(user);
}else {
throw new SystemException("该用户不存在");
}
}
}
4、LoginSuccessHandler (里面是登录成功进行的处理,可以存储自定义token,进行进一步用户验证等)
@Component
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Value("${token.tokenExpireTime}")
private Integer tokenExpireTime;
@Autowired
private RedisUtil redisUtil;
@Autowired
private GlobalValueConfig VALUECONFIG;
@Autowired
private IUserService iUserService;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws ServletException, IOException {
SecurityUser user = (SecurityUser) auth.getPrincipal();
String username = user.getUsername();
//登录成功,先检查之前是否已经有该用户的登录信息,有的话清掉
String oldToken = (String)redisUtil.hget(VALUECONFIG.UserToken, username);
if(null!=oldToken){
redisUtil.hdel(VALUECONFIG.UserToken,username);
redisUtil.hdel(VALUECONFIG.TokenInfo,oldToken);
}
// 将token存储到redis
String token = null;
token = UUID.randomUUID().toString().replace("-","");
boolean set1 = redisUtil.hset(VALUECONFIG.TokenInfo,token, user, tokenExpireTime);
boolean set2 = redisUtil.hset(VALUECONFIG.UserToken,username, token, tokenExpireTime);
//将token返回给前端
ResponseUtil.out(response,ResponseUtil.resultMap(true,200,token));
}
}
5、LoginFailHandler(exception有很多种类型,可以判断出用户是因为什么登录失败的,我这里没有写那么详细,如果有需要可以细分)
@Component
public class LoginFailHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
System.out.println(exception);
ResponseUtil.out(response,ResponseUtil.resultMap(false,500,"登录失败"));
}
}
6、MyAuthenticationFilter (其他请求的合法校验)
/**
* @Author: WXM
* @Description:
* @Date: create in 2021/3/10 10:34
*/
public class MyAuthenticationFilter extends BasicAuthenticationFilter {
private RedisUtil redisUtil = GetBeanUtil.getBean(RedisUtil.class);
private GlobalValueConfig VALUECONFIG = GetBeanUtil.getBean(GlobalValueConfig.class);
public MyAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
public MyAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationManager, authenticationEntryPoint);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String wxmToken = request.getHeader(VALUECONFIG.TokenName);
//如果token为空,则需要让用户登录
if(StringUtils.isBlank(wxmToken)){
chain.doFilter(request,response);
return;
}
User user = (User)redisUtil.hget(VALUECONFIG.TokenInfo,wxmToken);
if(null!=user){
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user,null,new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request,response);
}else {
ResponseUtil.out(response,ResponseUtil.resultMap(false,401,"登录信息过期,请重新登录"));
}
}
}