本文章主要针对以下四个方面进行讲解,包括认证与授权
项目目录结构如下:
SpringSecurity配置部分 (重点) :
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() // 权限配置
.antMatchers(
).permitAll()//配置免拦截接口
.anyRequest().authenticated() // 其他的需要登陆后才能访问
.and().httpBasic().authenticationEntryPoint(userNotLoginHandler) // 配置未登录处理类
.and().formLogin().loginProcessingUrl("/api/login")// 配置登录URL
.successHandler(userLoginSuccessHandler) // 配置登录成功处理类
.failureHandler(userLoginFailureHandler) // 配置登录失败处理类
.and().logout().logoutUrl("/api/logout")// 配置登出地址
.logoutSuccessHandler(userLogoutSuccessHandler) // 配置用户登出处理类
.and().exceptionHandling().accessDeniedHandler(userAccessDeniedHandler)// 配置没有权限处理类
.and().cors()// 开启跨域
.and().csrf().disable(); // 禁用跨站请求伪造防护
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 禁用session(使用Token认证)
http.headers().cacheControl(); // 禁用缓存
http.addFilter(new JWTAuthenticationFilter(authenticationManager())); //// 添加JWT过滤器
}
- 忽略不需要认证与授权的Url
for (String url : ignoreUrlsConfig.getUrls()) {
registry.antMatchers(url).permitAll();
}
- 登入认证部分(重点)
UsernamePasswordFilter: 过滤器类,主要认证用户名和密码。
Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。
AuthenticationManager接口:定义了认证Authentication的方法
UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。
UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。
成功处理器 AuthenticationSuccessHandler实现:
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
SysUserDetails sysUserDetails = (SysUserDetails) authentication.getPrincipal();
// 获得请求IP
String ip = AccessAddressUtils.getIpAddress(request);
sysUserDetails.setIp(ip);
String token = JWTTokenUtils.createAccessToken(sysUserDetails);
// 保存Token信息到Redis中
JWTTokenUtils.setTokenInfo(token, sysUserDetails.getUsername(), ip);
log.info("用户{}登录成功,Token信息已保存到Redis", sysUserDetails.getUsername());
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put("token", token);
tokenMap.put("openId", sysUserDetails.getUsername());
ResponseUtils.responseJson(response, ResponseUtils.response(200, "登录成功", tokenMap));
}
}
失败处理器 AuthenticationFailureHandler实现:
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) {
ResponseUtils.responseJson(response, ResponseUtils.response(500, "登录失败", exception.getMessage()));
}
}
- 注销Url部分
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
// 添加到黑名单
String token = request.getHeader(JWTConfig.tokenHeader);
JWTTokenUtils.addBlackList(token);
log.info("用户{}登出成功,Token信息已保存到Redis的黑名单中", JWTTokenUtils.getUserNameByToken(token));
SecurityContextHolder.clearContext();
ResponseUtils.responseJson(response, ResponseUtils.response(200, "登出成功", null));
}
}
- 授权部分
在ExceptionTranslationFilter类中,根据异常信息,识别出是未登入出现异常,还是无权限出现异常,源码逻辑如下:
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception);
} else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!this.authenticationTrustResolver.isAnonymous(authentication) && !this.authenticationTrustResolver.isRememberMe(authentication)) {
this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
} else {
this.logger.debug("Access is denied (user is " + (this.authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception);
this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource")));
}
}
}
我们可以利用自己实现的处理器,如未登入处理器(AuthenticationEntryPoint)和 权限不足处理器(AccessDeniedHandler),实现代码如下:
// 未登入处理器
public class UserNotLoginHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
ResponseUtils.responseJson(response, ResponseUtils.response(401, "未登录", authException.getMessage()));
}
}
// 权限不足处理器
public class UserAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
ResponseUtils.responseJson(response, ResponseUtils.response(403, "拒绝访问", accessDeniedException.getMessage()));
}
}
可以实现PermissionEvaluator接口类,来验证某个用户是否有访问Url的权限
public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
SysUserDetails sysUserDetails = (SysUserDetails) authentication.getPrincipal();
// 用户权限
Set<String> permissions = new HashSet<String>();
List<SysUser> list = sysUserService.findRoleByOpenId(sysUserDetails.getOpenid());
list.forEach(auth -> {
permissions.add(String.valueOf(auth.getRole()));
});
// 判断是否拥有权限
if (permissions.contains(permission.toString())) {
return true;
}
return false;
}