SpringSecurity的相关配置
SpringSecurity配置类
- 主要定义3个配置,分别是
- SecurityFilterChain,自定义login登录接口,并且放行,其他接口则需要通过
SpringSecurity
来做安全鉴权
- AuthenticationManager,配置授权管理器,固定配置,复制配置即可
- BCryptPasswordEncoder,配置密码加解密实现,例如这里使用BCrypt实现加解密,配置后
SpringSecurity
会在我们自定义的UserDetailsService
的loadUserByUsername()
方法回调后,进行密码校验
/**
* SpringSecurity配置类
*/
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
// 登录接口,放行
.antMatchers("/security/login")
.permitAll();
// 开发阶段,关闭csrf防护
http.csrf().disable();
// 关闭session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 关闭缓存
http.headers().cacheControl().disable();
return http.build();
}
/**
* 配置授权管理器
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* 密码加密配置
*/
@Bean
public BCryptPasswordEncoder bcryptPasswordEncoder() {
// BCrypt密码加密
return new BCryptPasswordEncoder();
}
}
自定义SpringSecurity的认证用户
-
SpringSecurity
框架要求我们返回一个UserDetails
接口的实现类,一般我们会创建一个类来实现该接口,当然也可以直接使用框架内部默认实现的User
类,但自定义实现类,可以自定义属性,例如昵称
、邮箱
、权限列表
等字段
/**
* 自定义SpringSecurity的认证用户
*/
@Data
@NoArgsConstructor
public class UserAuth implements UserDetails {
/**
* 用户Id
*/
private String id;
/**
* 用户账号
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 权限内置
*/
private List<SimpleGrantedAuthority> authorities;
/**
* 用户类型(00系统用户)
*/
private String userType;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户邮箱
*/
private String email;
/**
* 真实姓名
*/
private String realName;
/**
* 手机号码
*/
private String mobile;
/**
* 用户性别(0男 1女 2未知)
*/
private String sex;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 备注
*/
private String remark;
/**
* 部门编号【当前】
*/
private String deptNo;
/**
* 职位编号【当前】
*/
private String postNo;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (authorities == null) {
return null;
}
// 把角色类型进行转换,统一给权限标识符加上前缀,然后转换为SimpleGrantedAuthority类型返回
return authorities.stream().map(role -> {
return new SimpleGrantedAuthority("ROLE_" + role);
}).collect(Collectors.toList());
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@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接口实现类
- 重写
loadUserByUsername()
方法,通过用户名查询用户,如果不存在则返回null或者抛出异常,SpringSecurity
会进行判断,如果为null或者抛出了异常,会包装成一个InternalAuthenticationServiceException
,此时我们定义一个全局异常处理器,统一返回错误信息即可
- 注:要通过
@Component
注解,放到iOC容器中,SpringSecurity
框架才能找到该实现类
/**
* 自定义SpringSecurity的UserDetailsService实现类
* 主要是重写loadUserByUsername方法,该方法会被SpringSecurity调用,用于根据用户名查找用户信息
*/
@Component
public class MyUserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
/**
* 注意:这里不需要做密码校验,SpringSecurity会根据我们SecurityConfig配置的BCryptPasswordEncoder做密码校验
*/
@Override
public UserDetails loadUserByUsername(String username) {
// 根据用户名,查找用户信息
User user = userMapper.findUserVoForLogin(username);
// 用户不存在,或账号停用,则返回登录失败
if (user == null || "1".equals(user.getDataState())) {
throw new RuntimeException("用户登录失败");
}
// 实体类转UserAuth
return BeanUtil.toBean(user, UserAuth.class);
}
}
全局异常处理器
/**
* 全局异常处理器
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 省略其他异常处理...
/**
* 处理其他未知异常。
* 返回HTTP响应状态码500,包含错误代码和异常堆栈信息。
*
* @param exception 未知异常
* @return 响应数据,包含错误代码和异常堆栈信息
*/
@ExceptionHandler(Exception.class)
public ResponseResult<Object> handleUnknownException(Exception exception) {
exception.printStackTrace();
if (ObjectUtil.isNotEmpty(exception.getCause())) {
log.error("其他未知异常:{}", exception.getMessage());
}
return ResponseResult.error(500, exception.getMessage());
}
}
Token拦截器
- 该SpringMVC拦截器,主要是用来进行
token
校验,如果校验通过,则将token中携带的userId
,保存到ThreadLocal
中,后续接口中就可以通过ThreadLocal
来获取userId
/**
* 管理后台端,用户Token校验拦截器
*/
@Component
public class UserTokenInterceptor implements HandlerInterceptor {
/**
* JWT配置
*/
@Autowired
private JwtTokenManagerProperties jwtTokenManagerProperties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
// 从请求头中,获取Token
String token = request.getHeader(Constants.USER_TOKEN);
// Token不为空,则校验Token
if (!EmptyUtil.isNullOrEmpty(token)) {
// 解析Token
Claims claims = JwtUtil.parseJWT(jwtTokenManagerProperties.getBase64EncodedSecretKey(), token);
// 获取Token中保存的用户信息,由于存的时候是一个json字符串,所以将类型转成字符串
String currentUserJson = String.valueOf(claims.get(Constants.CURRENT_USER));
// 将用户信息,放入ThreadLocal中,后续就可以通过ThreadLocal直接获取用户信息
UserThreadLocal.setSubject(currentUserJson);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清理ThreadLocal
UserThreadLocal.removeSubject();
}
}
配置拦截器
- 创建拦截器类后,还要配置到
WebMvcConfigurer
中,才能让拦截器生效
-
excludePathPatterns
属性,代表不拦截的url,例如登录接口不需要拦截
/**
* WebMvc高级配置
*/
@Configuration
@ComponentScan("springfox.documentation.swagger.web")
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 管理后台端,接口放行白名单
*/
private static final String[] ADMIN_EXCLUDE_PATH_PATTERNS = new String[]{
// 放行登录接口
"/security/**"
};
/**
* 管理后台端,用户Token校验拦截器
*/
@Autowired
private UserTokenInterceptor userTokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 管理后台端,配置用户的Token鉴权拦截器
registry.addInterceptor(userTokenInterceptor)
.excludePathPatterns(ADMIN_EXCLUDE_PATH_PATTERNS)
.addPathPatterns("/**");
}
}
实现登录
用户表Vo
/**
* 用户表Vo
*/
@Data
@NoArgsConstructor
public class UserVo extends BaseVo {
@ApiModelProperty(value = "用户账号")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "用户类型(0:系统用户,1:客户)")
private String userType;
@ApiModelProperty(value = "用户昵称")
private String nickName;
@ApiModelProperty(value = "用户邮箱")
private String email;
@ApiModelProperty(value = "真实姓名")
private String realName;
@ApiModelProperty(value = "手机号码")
private String mobile;
@ApiModelProperty(value = "用户性别(0男 1女 2未知)")
private String sex;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "三方openId")
private String openId;
@ApiModelProperty(value = "查询用户:用户角色Ids")
private Set<String> roleVoIds;
@ApiModelProperty(value = "用户令牌")
private String userToken;
}
LoginController
/**
* 登录相关接口
*/
@RestController
@Api(tags = "用户登录")
@RequestMapping("/security")
public class LoginController {
@Autowired
private LoginService loginService;
@PostMapping("/login")
@ApiOperation(value = "用户登录", notes = "用户登录")
public ResponseResult<UserVo> login(@RequestBody LoginDto loginDto) {
return ResponseResult.success(loginService.login(loginDto));
}
}
LoginService接口
/**
* 登录业务层接口
*/
public interface LoginService {
/**
* 后台用户登录
*/
UserVo login(LoginDto loginDto);
}
LoginServiceImpl实现类
/**
* 登录业务层实现类
*/
@Service
public class LoginServiceImpl implements LoginService {
/**
* 授权管理器
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* JWT配置
*/
@Autowired
private JwtTokenManagerProperties jwtTokenManagerProperties;
/**
* SpringSecurity的配置信息
*/
@Autowired
private SecurityConfigProperties securityConfigProperties;
/**
* Redis
*/
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public UserVo login(LoginDto loginDto) {
// 将用户名和密码进行包装,也就是加密
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginDto.getUsername(),
loginDto.getPassword()
);
// 使用认证管理器,来进行认证
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 认证失败
if (!authenticate.isAuthenticated()) {
throw new BaseException(BasicEnum.LOGIN_FAIL);
}
// 认证成功,获取当前登录的用户信息
UserAuth userAuth = (UserAuth) authenticate.getPrincipal();
// 属性拷贝
UserVo userVo = BeanUtil.toBean(userAuth, UserVo.class);
// 敏感数据处理,不返回密码信息
userVo.setPassword("");
// Token保存用户信息
Map<String, Object> claims = new HashMap<>();
// 将用户信息转为json字符串,后续会在UserTokenIntercept拦截器中,解析token,并获取这个用户信息数据
claims.put(Constants.CURRENT_USER, JSONUtil.toJsonStr(userVo));
// 生成Token
String jwtToken = JwtUtil.createJWT(
jwtTokenManagerProperties.getBase64EncodedSecretKey(),
jwtTokenManagerProperties.getTtl(),
claims
);
// 保存Token到用户vo中
userVo.setUserToken(jwtToken);
return userVo;
}
}
用户表Mapper
@Mapper
public interface UserMapper {
// 省略其他方法...
/**
* 根据用户名,查找用户信息
*
* @param username 用户名
*/
@Select("select * from sys_user where username = #{username}")
User findUserVoForLogin(String username);
}
附录
单词 |
音标 |
解释 |
Security |
səˈkjʊrəti |
安全 |
Authentication |
ɔːˌθentɪˈkeɪʃən |
身份认证,衍生词:Authenticated(被认证过的) |
Authorization |
ˌɔːθəraɪˈzeɪʃən |
访问授权,衍生词:Authorize、Authority |
Permit |
ˈpɜːmɪt |
许可证 |
Matchers |
ˈmætʃərz |
匹配器 |
Granted |
ɡræntɪd |
授予特定的权限 |
Principal |
ˈprɪnsəpl |
被认证和授权访问资源或系统的实体或用户 |