// 在配置类中注入的加密
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisUtils redisUtils;
public R login(User user){
// 将用户名和密码丢给authenticationManager进行验证
UsernamePasswordAuthenticationToken upat = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(upat);
// 如果验证没通过,authenticate就会是null
if(Objects.isNull(authenticate)){
// 这里就会将错误丢到认证失败的异常丢给我们配置好的异常处理器,返回异常对应的结果给前端
throw new RuntimeException("登陆失败");
}
// 验证通过返回的authenticate可以获得一个UserDetails对象(这个就是自己写的实现类)
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
User auth_user = loginUser.getUser();
String id = auth_user.getId().toString();
String jwt = JwtUtils.createJWT(id);
// 将用户信息存入redis
redisUtils.setCacheObject("login:"+id, loginUser);
Map<String, String>map = new HashMap<>();
map.put("msg", jwt);
return R.ok(map);
}
}
@Service
public class MyUserDetailService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
MenuMapper menuMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<com.geology.geology_system_server.pojo.User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
com.geology.geology_system_server.pojo.User user = userMapper.selectOne(wrapper);
if(user == null){
throw new UsernameNotFoundException("无该用户");
}
System.out.println("鉴权成功");
// 从数据库中查询用户的权限信息
List<String> permission = menuMapper.selectPermsByUserId(user.getId());
// 默认的User类也是UserDetails的实现类
// 返回查询到的用户的用户名和密码封装成User对象,这个User对象可以是自己写的,只要是实现了UserDetails就可以
LoginUser loginUser = new LoginUser(user, permission);
return loginUser;
}
}
@NoArgsConstructor
@Data
public class LoginUser implements UserDetails {
private User user;
private List<String> permissions; // 数据库传来的权限字符串信息
// 这个不需要存进redis中
@JSONField(serialize = false)
private List<GrantedAuthority> authorities = null;
public LoginUser(User user, List<String> permission){
this.user = user;
this.permissions = permission;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 如果已经创建过权限集合,下次来就不需要再遍历数据库查来的权限字符串了
if (authorities!=null){
return authorities;
}
// 将权限信息封装到GrantedAuthority的实现类中
// authorities = new ArrayList<>();
// for (String permission : permissions) {
// SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);
// authorities.add(simpleGrantedAuthority);
// }
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;
}
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 密码加密
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Autowired
JwtFilter jwtFilter;
@Autowired
AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
AccessDeniedHandler accessDeniedHandler;
// 有关放行的配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
// 不通过session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/login").anonymous() // anonymous表示未登录才能访问,如果登录则不能访问
// .antMatchers("/user").hasAuthority("admin") //也可以在这里进行权限配置,和注解那个是一样的
// 除此之外都要进行鉴权认证
.anyRequest().authenticated();
// 将jwt过滤器添加到usernamepas...之前
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
// 配置认证失败和授权失败的处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
// 允许跨域
http.cors();
}
}
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
RedisUtils redisUtils;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)){
// 如果没有token,也放行,因为后面会有别的过滤器来过滤token
filterChain.doFilter(request, response);
return;
}
// 解析token
String userId;
try {
Claims claims = JwtUtils.parseJWT(token);
userId = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token异常");
}
// 从redis中获取token
String userKey = "login:" + userId;
LoginUser loginUser = redisUtils.getCacheObject(userKey);
if(Objects.isNull(loginUser)){
// redis中未查到
throw new RuntimeException("用户未登录");
}
// 如果redis中查到了,就存入SecurityContextHolder,还有对应的权限信息
UsernamePasswordAuthenticationToken upat = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(upat);
// 放行
filterChain.doFilter(request,response);
}
}
这里是否在redis中查到都放行了,但是如果查到了,会将LoginUser对象和对应的权限信息放进SecurityContextHolder中,如果没放的话,后续自有filter会处理认证失败的情况
之后要在Security配置类中将JwtFilter放在UsernamePasswordAuthenticationFilter之前,这样每次来就会先经过这个filter
思路:
public R logout(){
// 从ContextHolder中获取授权对象
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
boolean suc = redisUtils.deleteObject("login:" + loginUser.getUser().getId());
if(suc){
R.ok("登出成功");
}
return R.error("登录失败");
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
@PreAuthorize("hasAnyAuthority('data:write', 'data:read')") // 有这些权限中的任意一个就可以访问
// @PreAuthorize("hasAuthority('role')") // 有这个权限才可以访问
// @PreAuthorize("hasRole('admin')") // 会 将ROLE_ 这个拼接到用户权限之前,最终看有没ROLE_admin这个权限
// @PreAuthorize("@myExpression.hasAuthority('data:write')") // 通过获得到注入了的bean中的方法(自定义的校验方法)来判断
@GetMapping("/hello")
........
@NoArgsConstructor
@Data
public class LoginUser implements UserDetails {
private User user;
private List<String> permissions; // 数据库传来的权限字符串信息
// 这个不需要存进redis中
@JSONField(serialize = false)
private List<GrantedAuthority> authorities = null;
public LoginUser(User user, List<String> permission){
this.user = user;
this.permissions = permission;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 如果已经创建过权限集合,下次来就不需要再遍历数据库查来的权限字符串了
if (authorities!=null){
return authorities;
}
// 将权限信息封装到GrantedAuthority的实现类中
// authorities = new ArrayList<>();
// for (String permission : permissions) {
// SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);
// authorities.add(simpleGrantedAuthority);
// }
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;
}
}
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 授权失败的处理器
R res = R.error(HttpStatus.FORBIDDEN.value(), "您的权限不够");
WebUtils.renderString(response, JSON.toJSONString(res));
}
}
@Component
//异常处理
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// 认证失败的处理器
R res = R.error(HttpStatus.UNAUTHORIZED.value(), "认证失败");
WebUtils.renderString(response, JSON.toJSONString(res));
}
}