无聊复习了一下Spring Security
自己想法,大部分人都不会点退出登录,这是旧token还在,会不会存在安全隐患,然后一般账号可以多个人登录,两个人·的·话那么其中一个退出的话另一个也被迫了,我觉得用uuid好一点
首先Login类要继承框架的接口,要实现如下,权限方法重写是把数据库中查询的权限返回给框架
@Data
@AllArgsConstructor
@NoArgsConstructor
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;
}
public LoginUser(User user){
this.user=user;
}
//权限,不会序列化到redis,弄这个就可以permissions,不然会报错
@JSONField(serialize = false)
private List<SimpleGrantedAuthority> authorities;
//权限方法重写
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// List<GrantedAuthority> newList=new ArrayList<>();
// for (String permission : permissions) {
// SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
// newList.add(authority);
// }
if(authorities!=null){
return authorities;
}
//把permissions权限信息转换存入
authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
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;
}
}
继承UserDetailsService ,重写方法,返回LoginUser对象,权限也是这里添加
/**
* @Author 菠萝哥
**/
@Service
public class UserDetailserviceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,username);
User user = userMapper.selectOne(queryWrapper);
if (Objects.isNull(user)){
throw new RuntimeException("用户名密码错误");
}
//TODO 获取权限信息
List<String> list=new ArrayList<>(Arrays.asList("test","admin"));
return new LoginUser(user,list);
}
controller层,
/**
* @Author 菠萝哥
**/
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
@PostMapping("/user/login")
public ResponseResult login(@RequestBody User user){
//登录
return loginService.login(user);
}
@GetMapping("/user/logout")
public ResponseResult logout(){
return loginService.logout();
}
}
serviceimpl,这里首先要去config类里面定义
AuthenticationManageji然后将前端穿过来的username,password穿进去,框架会调用UserDetailsServiceimpl方法,返回loginUser对象,这里面包含了用户信息和权限,框架会自己判断密码什么的,从authrnticate里面取出loginUser对象,封装信息返回给前端,基础功能已经完成了
/**
* @Author 菠萝哥
**/
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Override
public ResponseResult login(User user) {
//进行认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//如果认证不通过,
if(Objects.isNull(authenticate)) {
throw new RuntimeException("登录失白");
}
//通过,生成jwt
LoginUser loginUser =(LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
//把完整的用户消息存入redis
HashMap<String, String> map = new HashMap<>();
redisCache.setCacheObject("login:"+userId,loginUser);
map.put("token",jwt);
LoginUser object =(LoginUser) redisCache.getCacheObject("login:" + userId);
System.out.println(object);
return new ResponseResult(200,"登录成功",map);
}
@Override
public ResponseResult logout() {
//获取用户信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser =(LoginUser) authentication.getPrincipal();
Long userid = loginUser.getUser().getId();
//删除redis的值
redisCache.deleteObject("login:"+userid);
return new ResponseResult(200,"退出成功");
}
}
异常处理,这个是密码相关的错误,
/**
* @Author 菠萝哥
**/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
//处理异常
ResponseResult result = new ResponseResult<>(401,"用户密码错误,请重新登录");
String jsonString = JSON.toJSONString(result);
WebUtils.renderString(response,jsonString);
}
}
权限错误异常
/**
* @Author 菠萝哥
**/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
//处理异常
ResponseResult result = new ResponseResult<>(401,"授权失败,权限不足");
String jsonString = JSON.toJSONString(result);
WebUtils.renderString(response,jsonString);
}
}
过滤器,
/**
* @Author 菠萝哥
**/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
//空为false
if (!StringUtils.hasText(token)) {
//放行
filterChain.doFilter(request, response);
return;
}
//解析token
String userId;
try {
Claims claims = JwtUtil.parseJWT(token);
userId = claims.getSubject();
} catch (Exception e) {
throw new RuntimeException("token非法");
}
//凑redis获取用户信息
String redisKey = "login:" + userId;
LoginUser loginUser =(LoginUser) redisCache.getCacheObject(redisKey);
//为null
if (Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
//存入SecurityContextHolder
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
filterChain.doFilter(request,response);
}
}
权限判断
/**
* @Author 菠萝哥
**/
@RestController
public class helloController {
@GetMapping("/hello")
//@PreAuthorize("hasAuthority('test')")
@PreAuthorize("@qx.hasAuthority('system:dept:list')")
public String hello(){
return "hello";
}
}
/**
* @Author 菠萝哥
**/
@Component("qx")
public class ExpressionRoot {
public boolean hasAuthority(String authority){
//获取用户权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
List<String> permissions = loginUser.getPermissions();
//判断用户权限集合是否存在authority
return permissions.contains(authority);
}
}
配置类
/**
* @Author 菠萝哥
**/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启权限校验
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//创建注入容器
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/user/login").anonymous()
//必须具有这个权限才能访问
.antMatchers("/testCors").hasAuthority("system:dept:list222")
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//配置异常处理器
http.exceptionHandling()
//配置认证失败处理器
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
//允许跨域
http.cors();
}
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
}