国际惯例导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
添加security配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter { //过时了不影响使用
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.and()
.authorizeHttpRequests(authz -> authz
.antMatchers("/user/**").permitAll()
.anyRequest().authenticated());
}
}
实现UserDetailService接口,自定义查询逻辑,一般是从数据库中查询
public Class UserDetailsServiceImpl implement UserDetailsService{
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<SysUser> user = sysUserMapper.selectOne(c -> c.where(sysUser.userName, isEqualToWhenPresent(username)));
if(!user.isPresent()){
// 用户名或密码不正确抛出异常
throw ... ;
}
/*根据用户Id或用户名,查询用户的权限列表*/
int userId = user.get().getId();
......
return new MyUserDetail(user.get(),permissions);
}
该方法需要返回UserDetails对象,所以需要我们自定义实现该接口
@Data
@NoArgsConstructor
public class MyUserDetail implements UserDetails {
private SysUser user;
private List<String> permission;
private List<SimpleGrantedAuthority> authorities;
public MyUserDetail(SysUser user, List<String> permission) {
this.user = user;
this.permission = permission;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if(authorities!=null){
return authorities;
}
authorities = permission.stream().map(c -> new SimpleGrantedAuthority(c)).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;
}
}
该对象用来保存数据库查询到的用户信息,包括用户名密码和权限等
将前端传过来的用户名和密码封装成Authentication对象(Security认证对象)
/**
* 参数1:userForm.getUserName 前端传过来的用户名
* 参数2:userForm.getUserName 前端传过来的用户密码
*/
@Service
public UserServiceImpl implements UserService{
@Autowired
private AuthenticationManager authenticationManeger;
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userForm.getUserName(),userForm.getPassword());
Authentication authenticate = authenticationManager.authenticate(auth);
/**Security框架会将authenticate对象中的密码与之前从数据库查询的MyUserDetails对象的密码作比较
(根据我们在配置类中指定的加密方式:BCryptPasswordEncoder),
如果用户名和密码相同,则把MyUserDetail对象的权限信息封装到authenticate对象中,不同则抛出异常信息
*/
if(Objects.isNull(authenticate)){
throw new SsTestException(ErrorStatusCode.NO_AUTHENTION);
}
// 如果存在就将用户的id生成jwtToken字符串,存入到redis中
// 从认证对象中获取
MyUserDetail userDetailser=(MyUserDetail) authenticate.getPrincipal();
SysUser user = userDetailser.getUser();
// 使用JWT工具类,根据authenticate对象中的用户Id生成token字符串
String jwt = JwtUtil.createJWT(user.getId().toString());
// 将认证对象存入到redis
redisCache.setCacheObject("login:"+user.getId(),userDetailser);
// 将token字符串返回给前端
return jwt;
定义一个过滤器,用来校验前端请求的token字符串和获取redis中的认证对象
/**
* token认证过滤器
*/
@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");
if(StrUtil.isBlank(token)){
filterChain.doFilter(request,response);
return;
}
// 解析token
String userId;
try {
Claims claims = JwtUtil.parseJWT(token);
userId = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
// 抛出异常
throw ....;
}
// 从redis中获取认证对象
String key="login:"+userId;
MyUserDetail userDetail = redisCache.getCacheObject(key);
if(Objects.isNull(userDetail)){
throw new RuntimeException("用户未登录");
}
// 存入SecurityContextHolder
// 获取权限信息封装到Authentication中
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(userDetail,null,userDetail.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 放行
filterChain.doFilter(request,response);
}
}
将自定义的过滤器添加到security的过滤器链中
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(){
return new UserDatailsServiceImpl();
}
@Override
public void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder())
;
}
//作用: 用来将自定义AuthenticationManager在工厂中进行暴露,可以在任何位置注入
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //关闭session管理
.and()
.authorizeHttpRequests(authz -> authz.antMatchers("/user/**").permitAll()
.anyRequest().authenticated()).addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);
}
}