一 、SpringSecurity过滤器链
spring security本质上是一个过滤器链,我们的工作就是负责自定义实现其中的一些过滤器,然后加入一些配置类。
二、Spring Security认证完整过程
登录
1.controller
@Autowired
private LoginService loginService;
@PostMapping("/user/login")
public ResponseResult login(@RequestBody User user){
// 登录
return loginService.login(user);
}
2.service
注:这里对应认证流程中的第一步,封装用户名和密码到一个Authentication对象中,但是Authentication是一个接口,所以我们new一个他的实现类。
authenticationManager.authenticate(authenticationToken) 来验证和数据库中的用户名和密码是否正确。如果认证通过的话,这里会返回一个UserDetails对象。
authenticate 会调用 UserDetailsService接口中的 loadUserByUsername(String username) throws UsernameNotFoundException;
但是我们不能使用jdk中的实现方法,因为他是去内存中查询,我们需要自定义实现类,来实现 loadUserByUsername(String username); 见下面。
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Override
public ResponseResult login(User user){
// AuthenticationManager authenticate进行用户认证
// 将登录时的用户名和密码封装成 authentication
// Authentication 是一个接口 需要使用 UsernamePasswordAuthenticationToken实现类 来封装用户名和密码
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken); // 这个地方经过一些列流程 到达 自己实现的 userDetailService 实现类 去查数据库,在这一步也是用到了SecurityConfig 中的PasswordEncoder
System.out.println(authenticate);
// 如果认证不通过,给出提示
if (Objects.isNull(authenticate)){
throw new RuntimeException("登录失败!");
}
//认证通过,使用userId 生成 jwt jwt存入ResponseResult
LoginUser loginUser = (LoginUser)authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtils.createJWT(userId);
HashMap<String,String> jwtMap = new HashMap<>();
jwtMap.put("token",jwt);
//用户信息 jwt 存入 redis
// System.out.println("loginService ====" + loginUser);
redisCache.setCacheObject("login:"+userId,loginUser);
return new ResponseResult(200,"success",jwtMap);
}
自定义UserDetailsService实现类
注:这里返回的LoginUser是自定义实体类,实现了UserDetails,看下面;
最终返回登录结果。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
public UserMapper userMapper;
@Autowired
public MenuMapper menuMapper;
// @Autowired
// public PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// System.out.println(username);
// 查询用户信息
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,username);
User user = userMapper.selectOne(queryWrapper);
// System.out.println(user)
// 如果没有查询到用户,就抛出异常
if(Objects.isNull(user)){
throw new RuntimeException("用户名不存在,或者密码错误!!!");
}
// TODO 查询权限信息
List<String> list = menuMapper.selectPermsByUserId(user.getId());
// 封装用户信息 到 userdetails
return new LoginUser(user,list); // 这里返回了userdetails对象 里面包含密码 要和登陆密码进行对比 ,不对则登陆失败
}
}
LoginUser实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
private User user;
private List<String> permissions;
public LoginUser(User user, List<String> permissions) {
this.user = user;
this.permissions = permissions;
}
@JSONField(serialize = false)
private List<GrantedAuthority> authorities;
// 返回权限信息
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if(authorities != null){
return authorities;
}
authorities = new ArrayList<>();
for (String permission : permissions) {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);
authorities.add(simpleGrantedAuthority);
}
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
// 是否过期
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
// 是否超时
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
JwtAuthenticationTokenFilter
当用户登陆后访问其他资源时,首先要通过这个过滤器来验证是否登录过,然后通过了就可以放行下面的过程。
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取token
String token = request.getHeader("token");
if(!StringUtils.hasText(token)){
filterChain.doFilter(request,response);
return;
}
// 解析token
String userId;
try {
Claims claims = JwtUtils.parseJWT(token);
userId = claims.getSubject(); // 因为之前时使用userId 生成的 token ,所以这里可以解析到 userId
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token 非法");
}
String redisKey = "login:" + userId;
System.out.println("redisKey =============== " + redisKey);
//从redis中获取用户信息
LoginUser loginUser = redisCache.getCacheObject(redisKey);
System.out.println("filterloginUser ====== " + loginUser);
if(Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
// 存入SecurityContexHolder
// 获取权限信息
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 放行
filterChain.doFilter(request,response);
}