Spring Security使用
1、介绍
Spring Security是用来进行身份验证和授权的框架,基于此框架可以快速实现需求,并且支持灵活扩展。
2、核心依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3、核心配置类
@Configuration
//用来开启@PreAuthorize注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
//token验证
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling()
//配置认证异常处理
.authenticationEntryPoint(authenticationEntryPoint).
//配置授权异常处理
accessDeniedHandler(accessDeniedHandler);
//允许跨域请求
http.cors();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//使用自定义userDetailsService以及密码加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
}
4、自定义userDetailsService
Spring Security通过该接口查询用户信息,下面是基本的根据用户名从数据库中读取用户信息,按照Spring Security规定的UserDetails类返回
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private MenuMapper menuMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1,从数据库中查询用户信息
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName, username);
User user = userMapper.selectOne(queryWrapper);
//如果没有查询到用户就抛出异常
if (Objects.isNull(user)) {
throw new RuntimeException("用户名或者密码错误");
}
List<String> authoritieslist = menuMapper.selectPermsByUserId(user.getId());
//3,封装成userdtails对象
return new LoginUser(user, authoritieslist);
}
}
@Data
@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;
}
//存储SpringSecurity所需要的权限信息的集合
@JSONField(serialize = false)
private List<GrantedAuthority> authorities;
//权限封装,授权的数据就是从这边来的
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (authorities != null) {
return authorities;
}
//把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中
/*另外一种方法*/
//authorities = new ArrayList<>();
//for(String permission : permissions){
// SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
// authorities.add(authority);
//}
/*链式转换方法*/
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;
}
}
5、登录、登出
/**
* @author taoxuefeng
* @date 2025-10-31
*/
@Service
public class LoginServiceImpl implements LoginServcie {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public ResponseResult login(User user) {
//构造token类提交Spring Security进行验证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
//验证过程会用到上面的UserDetailsServiceImpl类,查询用户是否存在,并且进行密码匹配
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if (Objects.isNull(authenticate)) {
throw new RuntimeException("用户名或密码错误");
}
//验证成功保存到redis,后续请求根据请求头token从redis中获取用户信息
//使用userid生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
//authenticate存入redis
redisTemplate.opsForValue().set("login:" + userId, JSON.toJSONString(loginUser), 1, TimeUnit.HOURS);
//把token响应给前端
HashMap<String, String> map = new HashMap<>();
map.put("token", jwt);
return new ResponseResult(200, "登陆成功", map);
}
@Override
public ResponseResult logout() {
//退出登录
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userid = loginUser.getUser().getId();
redisTemplate.delete("login:" + userid);
return new ResponseResult(200, "退出成功");
}
}
6、身份验证
登录返回token参数,根据请求头中的token进行验证
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@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 = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token非法");
}
//从redis中获取用户信息
String redisKey = "login:" + userid;
String userJson = redisTemplate.opsForValue().get(redisKey);
LoginUser loginUser = JSON.parseObject(userJson, LoginUser.class);
if (Objects.isNull(loginUser)) {
throw new RuntimeException("用户未登录");
}
//存入SecurityContextHolder
//获取权限信息封装到Authentication中
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}
7、授权
根据上述构造的UsernamePasswordAuthenticationToken里的authorities集合进行权限匹配
一般直接用@PreAuthorize注解
注意点:用hasRole默认会添加ROLE_前缀,并且从数据库中获取封装的时候要对应上
@RestController
public class HelloController {
@GetMapping("/hello")
@PreAuthorize("hasAuthority('userPerm')")
public String hello() {
return "hello";
}
@GetMapping("/hello2")
@PreAuthorize("hasAuthority('dataPerm')")
public String hello2() {
return "hello2";
}
}