Spring Security 访问控制 源码解析
上篇 Spring Security 登录校验 源码解析 分析了使用Spring Security时用户登录时验证并返回token过程,本篇分析下用户带token访问时,如何验证用户登录状态及权限问题
用户访问控制相对简单,本质同登录验证一样,均采用过滤器拦截请求进行验证
这里需要自定义过滤器JwtAuthenticationTokenFilter并自定义路径匹配器RequestMatcher ,JwtAuthenticationTokenFilter继承AbstractAuthenticationProcessingFilter,并实现attemptAuthentication、successfulAuthentication、unsuccessfulAuthentication方法
1 AbstractAuthenticationProcessingFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//判断访问是否需要过滤
if(!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if(this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}
Authentication authResult;
//调用子类JwtAuthenticationTokenFilter实现
try {
authResult = this.attemptAuthentication(request, response);
if(authResult == null) {
return;
}
//session控制策略,因为用jwt,故不进行深入分析
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
//验证失败,调用子类unsuccessfulAuthentication方法
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
//验证失败,调用子类unsuccessfulAuthentication方法
this.unsuccessfulAuthentication(request, response, var9);
return;
}
if(this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//验证成功,调用子类successfulAuthentication方法
this.successfulAuthentication(request, response, chain, authResult);
}
}
2 JwtAuthenticationTokenFilter
@Component
public class JwtAuthenticationTokenFilter extends AbstractAuthenticationProcessingFilter { @Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private ObjectMapper mapper; @Value("${jwt.header}")
private String tokenHeader; @Value("${jwt.tokenHead}")
private String tokenHead; @Value("${jwt.appExpiration}")
private Long appExpiration; @Value("${jwt.pcExpiration}")
private Long pcExpiration; public JwtAuthenticationTokenFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
setAuthenticationManager(authentication -> authentication);
} @Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
//获得header
String authHeader = httpServletRequest.getHeader(this.tokenHeader);
throwException(authHeader == null,
new TokenVerifyException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌缺失")); throwException(!authHeader.startsWith(this.tokenHead),
new TokenVerifyException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌格式不正确")); String authToken = authHeader.substring(this.tokenHead.length());
UserDetails userDetails;
try {
Claims claims = jwtTokenUtil.getClaimsFromToken(authToken); throwException(claims.getSubject() == null,
new TokenVerifyException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌格式不正确")); // 校验密码是否已修改
userDetails = this.verifyPasswordChanged(claims.getSubject(), claims.getIssuedAt()); } catch (ExpiredJwtException ex) {
this.refreshToken(httpServletResponse, authToken);
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_ACCESS_KEY_NOT_EFFECT, "令牌已过期");
} catch (UnsupportedJwtException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌错误");
} catch (MalformedJwtException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌格式不正确");
} catch (SignatureException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "签名错误");
} Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
((UsernamePasswordAuthenticationToken) authentication).setDetails(
new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
}
return this.getAuthenticationManager().authenticate(authentication);
} private void refreshToken(HttpServletResponse response, String oldToken) {
try {
Claims claims = jwtTokenUtil.getClaimsAllowedClockSkew(oldToken, Integer.MAX_VALUE); String username = claims.getSubject();
this.verifyPasswordChanged(username, claims.getIssuedAt()); String newToken;
if (JwtUser.LOGIN_WAY_APP.equals(claims.getAudience())) {
newToken = jwtTokenUtil.refreshToken(oldToken, appExpiration);
} else if (JwtUser.LOGIN_WAY_PC.equals(claims.getAudience())) {
newToken = jwtTokenUtil.refreshToken(oldToken, pcExpiration);
} else {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "未识别的客户端");
} response.addHeader(this.tokenHeader, this.tokenHead + newToken); } catch (ExpiredJwtException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_RELOGIN, "登录已过期");
} catch (UnsupportedJwtException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌错误");
} catch (MalformedJwtException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌格式不正确");
} catch (SignatureException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "签名错误");
}
} //验证密码是否变更
private UserDetails verifyPasswordChanged(String username, Date tokenCreateTime) {
JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.isCreatedBeforeLastPasswordReset(tokenCreateTime, user.getLastPasswordResetDate())) {
throw new TokenVerifyException(RESPONSE_CODE.BACK_CODE_RELOGIN, "密码变更,需重新登录");
}
return user;
} private void throwException(boolean expression, AuthenticationException ex) {
if (expression) {
throw ex;
}
} //认证结果放入缓存
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
} //认证失败,封装返回信息
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
BackResult<String> result = new BackResult<>();
result.setCode(RESPONSE_CODE.BACK_CODE_EXCEPTION.value); if (failed instanceof TokenVerifyException) {
TokenVerifyException ex = (TokenVerifyException) failed;
result.setCode(ex.getResponseCode().value);
result.setExceptions(ex.getMessage()); } else if (failed instanceof TokenParseException) {
TokenParseException ex = (TokenParseException) failed;
result.setCode(ex.getResponseCode().value);
result.setExceptions(ex.getMessage()); } else {
logger.error("Token校验异常", failed);
result.setExceptions(SystemConstant.HIDE_ERROR_TIP);
} response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
mapper.writeValue(response.getWriter(), result);
}
}
3 SkipPathRequestMatcher
public class SkipPathRequestMatcher implements RequestMatcher {
private OrRequestMatcher matchers;
private RequestMatcher processingMatcher;
public SkipPathRequestMatcher(@NotEmpty List<String> pathsToSkip, String processingPath) {
List<RequestMatcher> m = pathsToSkip.stream().map(AntPathRequestMatcher::new).collect(Collectors.toList());
matchers = new OrRequestMatcher(m);
processingMatcher = new AntPathRequestMatcher(processingPath);
}
@Override
public boolean matches(HttpServletRequest request) {
return !matchers.matches(request) && processingMatcher.matches(request);
}
}
WebSecurityConfig 类中定义SkipPathRequestMatcher
public static final String TOKEN_BASED_ENTRY_POINT = "/limit/**";
public static final String TOKEN_AUTH_ENTRY_POINT = "/auth/**";
public static final String TOKEN_LOGIN_ENTRY_POINT = "/login/**";
public static final String TOKEN_OPEN_ENTRY_POINT = "/open/**";
//路径匹配器,跳过/auth/**类请求,验证/limit/**类请求
@Bean
public SkipPathRequestMatcher skipPathRequestMatcher() {
List<String> pathsToSkip = Collections.singletonList(TOKEN_AUTH_ENTRY_POINT);
return new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_ENTRY_POINT);
}
//JwtAuthenticationTokenFilter设置路径匹配器
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter(skipPathRequestMatcher());
}
Spring Security 访问控制 源码解析的更多相关文章
- Spring Security 解析(七) —— Spring Security Oauth2 源码解析
Spring Security 解析(七) -- Spring Security Oauth2 源码解析 在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因 ...
- spring security 认证源码跟踪
spring security 认证源码跟踪 在跟踪认证源码之前,我们先根据官网说明一下security的内部原理,主要是依据一系列的filter来实现,大家可以根据https://docs.sp ...
- spring boot @Value源码解析
Spring boot 的@Value只能用于bean中,在bean的实例化时,会给@Value的属性赋值:如下面的例子: @SpringBootApplication @Slf4j public c ...
- Feign 系列(05)Spring Cloud OpenFeign 源码解析
Feign 系列(05)Spring Cloud OpenFeign 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/ ...
- 异步任务spring @Async注解源码解析
1.引子 开启异步任务使用方法: 1).方法上加@Async注解 2).启动类或者配置类上@EnableAsync 2.源码解析 虽然spring5已经出来了,但是我们还是使用的spring4,本文就 ...
- Spring @Import注解源码解析
简介 Spring 3.0之前,创建Bean可以通过xml配置文件与扫描特定包下面的类来将类注入到Spring IOC容器内.而在Spring 3.0之后提供了JavaConfig的方式,也就是将IO ...
- spring security 实践 + 源码分析
前言 本文将从示例.原理.应用3个方面介绍 spring data jpa. 以下分析基于spring boot 2.0 + spring 5.0.4版本源码 概述 Spring Security 是 ...
- 社交媒体登录Spring Social的源码解析
在上一篇文章中我们给大家介绍了OAuth2授权标准,并且着重介绍了OAuth2的授权码认证模式.目前绝大多数的社交媒体平台,都是通过OAuth2授权码认证模式对外开放接口(登录认证及用户信息接口等). ...
- api网关揭秘--spring cloud gateway源码解析
要想了解spring cloud gateway的源码,要熟悉spring webflux,我的上篇文章介绍了spring webflux. 1.gateway 和zuul对比 I am the au ...
随机推荐
- C#零基础入门-3-第一个控制台程序
打开VS2017 文件 新建 项目 模板选择Visual C# Windows 控制台应用程序 快速写入Console.WriteLine 输入cw,然后快速按tab键两次即可.
- 为什么CynosDB叫真正的云原生数据库?
本文由腾讯云数据库发表 注:本文摘自2018年11月22日腾讯云数据库CynosDB新品发布会的演讲实录.随着互联网信息的发展,大家也对云这个词汇也不是特别陌生了,作为全球首选的云服务厂商之一的腾讯云 ...
- 【原】Java学习笔记032 - 多线程
package cn.temptation; public class Sample01 { public static void main(String[] args) { /* * [进程]:正在 ...
- Linux(三)——Unix&Linux 的基础命令
Linux(三)--Unix&Linux 的基础命令 快捷键 Ctl-A 光标移动到行首 Ctl-C 终止命令 Ctl-D 注销登录 Ctl-E 光标移动到行尾 Ctl-U 删除光标到行首的所 ...
- Maven settings.xml
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...
- springboot 应用中静态资源下载
一. 场景介绍 Excel模板静态资源在,应用中的static文件夹中,文件名称包含中文; 需求:页面直接访问下载Excel模板. 二.目录结构 三.后台代码 @GetMapping("/d ...
- Neutron Vlan Network 学习
vlan network 是带 tag 的网络,是实际应用最广泛的网络类型. 下图是 vlan100 网络的示例. 1. 三个 instance 通过 TAP 设备连接到名为 brqXXXX ...
- Smart Indenter for VBE(64bits smart indent addin for VBA Editor),VBA开发必备的智能排版工具。
原始出处:www.cnblogs.com/Charltsing/p/SmartIndenter64.html 作者QQ: 564955427 最近更换电脑,改用64位office做开发.VBA代码美化 ...
- Python类和对象
目录 类与对象 其他方法 继承 经典类和新式类 派生 组合 接口.归一化设计与抽象类 继承实现的原理 子类中调用父类的方法 指名道姓 super()方法 多态与多态性 封装 单下划线开头 双下划线开头 ...
- python使用装饰器对文件进行读写操作'及遍历文件目录
'''使用装饰器对文件进行读写操作''' # def check_permission(func): # '''演示嵌套函数定义及使用''' # def wrapper(*args,**kwargs) ...