asp access网站建设源代码,高端 网站制作,深圳网站设计按天收费,徐州企业网站建设原创/朱季谦
在Spring Security权限框架里#xff0c;若要对后端http接口实现权限授权控制#xff0c;有两种实现方式。
一、一种是基于注解方法级的鉴权#xff0c;其中#xff0c;注解方式又有Secured和PreAuthorize两种。
Secured如#xff1a; 1 PostMapping(若要对后端http接口实现权限授权控制有两种实现方式。
一、一种是基于注解方法级的鉴权其中注解方式又有Secured和PreAuthorize两种。
Secured如 1 PostMapping(/test)2 Secured({WebResRole.ROLE_PEOPLE_W})3 public void test(){4 ......5 return null;6 } PreAuthorize如 1 PostMapping(save)2 PreAuthorize(hasAuthority(sys:user:add) AND hasAuthority(sys:user:edit))3 public RestResponse save(RequestBody Validated SysUser sysUser, BindingResult result) {4 ValiParamUtils.ValiParamReq(result);5 return sysUserService.save(sysUser);6 } 二、一种基于config配置类需在对应config类配置EnableGlobalMethodSecurity(prePostEnabled true)注解才能生效其权限控制方式如下 1 Override2 protected void configure(HttpSecurity httpSecurity) throws Exception {3 //使用的是JWT禁用csrf4 httpSecurity.cors().and().csrf().disable()5 //设置请求必须进行权限认证6 .authorizeRequests()7 //首页和登录页面8 .antMatchers(/).permitAll()9 .antMatchers(/login).permitAll()10 // 其他所有请求需要身份认证11 .anyRequest().authenticated();12 //退出登录处理13 httpSecurity.logout().logoutSuccessHandler(...);14 //token验证过滤器15 httpSecurity.addFilterBefore(...);16 } 这两种方式各有各的特点在日常开发当中普通程序员接触比较多的则是注解方式的接口权限控制。
那么问题来了我们配置这些注解或者类其security框是如何帮做到能针对具体的后端API接口做权限控制的呢
单从一行PreAuthorize(hasAuthority(sys:user:add) AND hasAuthority(sys:user:edit))注解上看是看不出任何头绪来的若要回答这个问题还需深入到源码层面方能对security授权机制有更好理解。
若要对这个过程做一个总的概述笔者整体以自己的思考稍作了总结可以简单几句话说明其整体实现以该接口为例 1 PostMapping(save)2 PreAuthorize(hasAuthority(sys:user:add))3 public RestResponse save(RequestBody Validated SysUser sysUser, BindingResult result) {4 ValiParamUtils.ValiParamReq(result);5 return sysUserService.save(sysUser);6 } 即认证通过的用户发起请求要访问“/save”接口若该url请求在配置类里设置为必须进行权限认证的就会被security框架使用filter拦截器对该请求进行拦截认证。拦截过程主要一个动作是把该请求所拥有的权限集与PreAuthorize设置的权限字符“sys:user:add”进行匹配若能匹配上说明该请求是拥有调用“/save”接口的权限那么就可以被允许执行该接口资源。 在springbootsecurityjwt框架中通过一系列内置或者自行定义的过滤器Filter来达到权限控制如何设置自定义的过滤器Filter呢例如可以通过设置httpSecurity.addFilterBefore(new JwtFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)来自定义一个基于JWT拦截的过滤器JwtFilter这里的addFilterBefore方法将在下一篇文详细分析这里暂不展开该方法大概意思就是将自定义过滤器JwtFilter加入到Security框架里成为其中的一个优先安全Filter代码层面就是将自定义过滤器添加到ListFilter filters。 设置增加自行定义的过滤器Filter伪代码如下 1 Configuration2 EnableWebSecurity3 EnableGlobalMethodSecurity(prePostEnabled true)4 public class SecurityConfig extends WebSecurityConfigurerAdapter {5 ......6 Override7 protected void configure(HttpSecurity httpSecurity) throws Exception {8 //使用的是JWT禁用csrf9 httpSecurity.cors().and().csrf().disable()10 //设置请求必须进行权限认证11 .authorizeRequests()12 ......13 //首页和登录页面14 .antMatchers(/).permitAll()15 .antMatchers(/login).permitAll()16 // 其他所有请求需要身份认证17 .anyRequest().authenticated();18 ......19 //token验证过滤器20 httpSecurity.addFilterBefore(new JwtFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);21 }22 } 该过滤器类extrends继承BasicAuthenticationFilter而BasicAuthenticationFilter是继承OncePerRequestFilter该过滤器确保在一次请求只通过一次filter而不需要重复执行。这样配置后当请求过来时会自动被JwtFilter类拦截这时将执行重写的doFilterInternal方法在SecurityContextHolder.getContext().setAuthentication(authentication)认证通过后会执行过滤器链FilterChain的方法chain.doFilter(request, response); 1 public class JwtFilter extends BasicAuthenticationFilter {2 3 Autowired4 public JwtFilter(AuthenticationManager authenticationManager) {5 super(authenticationManager);6 }7 8 Override9 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {10 // 获取token, 并检查登录状态11 // 获取令牌并根据令牌获取登录认证信息12 Authentication authentication JwtTokenUtils.getAuthenticationeFromToken(request);13 // 设置登录认证信息到上下文14 SecurityContextHolder.getContext().setAuthentication(authentication);15 16 chain.doFilter(request, response);17 }18 19 } 那么问题来了过滤器链FilterChain究竟是什么
这里先点进去看下其类源码 1 package javax.servlet;2 3 import java.io.IOException;4 5 public interface FilterChain {6 void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;7 } FilterChain只有一个 doFilter方法这个方法的作用就是将请求request转发到下一个过滤器filter进行过滤处理操作执行过程如下 过滤器链就像一条铁链中间的每个过滤器都包含对另一个过滤器的引用从而把相关的过滤器链接起来像一条链的样子。这时请求线程就如蚂蚁一样会沿着这条链一直爬过去-----即通过各过滤器调用另一个过滤器引用方法chain.doFilter(request, response)实现一层嵌套一层地将请求传递下去当该请求传递到能被处理的的过滤器时就会被处理处理完成后转发返回。通过过滤器链可实现在不同的过滤器当中对请求request做处理且过滤器之间彼此互不干扰。
Spring Security框架上过滤器链上都有哪些过滤器呢 可以在DefaultSecurityFilterChain类根据输出相关log或者debug来查看Security都有哪些过滤器如在DefaultSecurityFilterChain类中的构造器中打断点如图所示可以看到自定义的JwtFilter过滤器也包含其中 这些过滤器都在同一条过滤器链上即通过chain.doFilter(request, response)可将请求一层接一层转发处理请求接口是否授权的主要过滤器是FilterSecurityInterceptor其主要作用如下
1. 获取到需访问接口的权限信息即Secured({WebResRole.ROLE_PEOPLE_W}) 或PreAuthorize定义的权限信息
2. 根据SecurityContextHolder中存储的authentication用户信息来判断是否包含与需访问接口的权限信息若包含则说明拥有该接口权限
3. 主要授权功能在父类AbstractSecurityInterceptor中实现 我们将从FilterSecurityInterceptor这里开始重点分析Security授权机制原理的实现。
过滤器链将请求传递转发FilterSecurityInterceptor时会执行FilterSecurityInterceptor的doFilter方法 1 public void doFilter(ServletRequest request, ServletResponse response,2 FilterChain chain) throws IOException, ServletException {3 FilterInvocation fi new FilterInvocation(request, response, chain);4 invoke(fi);5 } 在这段代码当中FilterInvocation类是一个有意思的存在其实它的功能很简单就是将上一个过滤器传递过滤的requestresponsechain复制保存到FilterInvocation里专门供FilterSecurityInterceptor过滤器使用。它的有意思之处在于是将多个参数统一归纳到一个类当中其到统一管理作用你想若是N多个参数传进来都分散到类的各个地方参数多了代码多了方法过于分散时可能就很容易造成阅读过程中弄糊涂这些个参数都是哪里来了。但若统一归纳到一个类里就能很快定位其来源方便代码阅读。网上有人提到该FilterInvocation类还起到解耦作用即避免与其他过滤器使用同样的引用变量。
总而言之这个地方的设定虽简单但很值得我们学习一番将其思想运用到实际开发当中不外乎也是一种能简化代码的方法。
FilterInvocation主要源码如下 1 public class FilterInvocation {2 3 private FilterChain chain;4 private HttpServletRequest request;5 private HttpServletResponse response;6 7 8 public FilterInvocation(ServletRequest request, ServletResponse response,9 FilterChain chain) {10 if ((request null) || (response null) || (chain null)) {11 throw new IllegalArgumentException(Cannot pass null values to constructor);12 }13 14 this.request (HttpServletRequest) request;15 this.response (HttpServletResponse) response;16 this.chain chain;17 }18 ......19 } FilterSecurityInterceptor的doFilter方法里调用invoke(fi)方法 1 public void invoke(FilterInvocation fi) throws IOException, ServletException {2 if ((fi.getRequest() ! null)3 (fi.getRequest().getAttribute(FILTER_APPLIED) ! null)4 observeOncePerRequest) {5 //筛选器已应用于此请求每个请求处理一次所以不需重新进行安全检查 6 fi.getChain().doFilter(fi.getRequest(), fi.getResponse());7 }8 else {9 // 第一次调用此请求时需执行安全检查10 if (fi.getRequest() ! null observeOncePerRequest) {11 fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);12 }13 //1.授权具体实现入口14 InterceptorStatusToken token super.beforeInvocation(fi);15 try {16 //2.授权通过后执行的业务17 fi.getChain().doFilter(fi.getRequest(), fi.getResponse());18 }19 finally {20 super.finallyInvocation(token);21 }22 //3.后续处理23 super.afterInvocation(token, null);24 }25 } 授权机制实现的入口是super.beforeInvocation(fi)其具体实现在父类AbstractSecurityInterceptor中实现beforeInvocation(Object object)的实现主要包括以下步骤 一、获取需访问的接口权限这里debug的例子是调用了前文提到的“/save”接口其权限设置是PreAuthorize(hasAuthority(sys:user:add) AND hasAuthority(sys:user:edit))根据下面截图可知变量attributes获取了到该请求接口的权限 二、获取认证通过之后保存在 SecurityContextHolder的用户信息其中authorities是一个保存用户所拥有全部权限的集合 这里authenticateIfRequired()方法核心实现 1 private Authentication authenticateIfRequired() {2 Authentication authentication SecurityContextHolder.getContext()3 .getAuthentication();4 if (authentication.isAuthenticated() !alwaysReauthenticate) {5 ......6 return authentication;7 }8 authentication authenticationManager.authenticate(authentication);9 SecurityContextHolder.getContext().setAuthentication(authentication);10 return authentication;11 } 在认证过程通过后执行SecurityContextHolder.getContext().setAuthentication(authentication)将用户信息保存在Security框架当中之后可通过SecurityContextHolder.getContext().getAuthentication()获取到保存的用户信息 三、尝试授权用户信息authenticated、请求携带对象信息object、所访问接口的权限信息attributes传入到decide方法 decide()是决策管理器AccessDecisionManager定义的一个方法。 1 public interface AccessDecisionManager {2 void decide(Authentication authentication, Object object,3 CollectionConfigAttribute configAttributes) throws AccessDeniedException,4 InsufficientAuthenticationException;5 boolean supports(ConfigAttribute attribute);6 boolean supports(Class? clazz);7 } AccessDecisionManager是一个interface接口这是授权体系的核心。FilterSecurityInterceptor 在鉴权时就是通过调用AccessDecisionManager的decide()方法来进行授权决策若能通过则可访问对应的接口。
AccessDecisionManager类的方法具体实现都在子类当中包含AffirmativeBased、ConsensusBased、UnanimousBased三个子类 AffirmativeBased表示一票通过这是AccessDecisionManager默认类
ConsensusBased表示少数服从多数
UnanimousBased表示一票反对
如何理解这个投票机制呢
点进去AffirmativeBased类里可以看到里面有一行代码int result voter.vote(authentication, object, configAttributes) 这里的AccessDecisionVoter是一个投票器用到委托设计模式即AffirmativeBased类会委托投票器进行选举然后将选举结果返回赋值给result然后判断result结果值若为1等于ACCESS_GRANTED值时则表示可一票通过也就是允许访问该接口的权限。
这里ACCESS_GRANTED表示同意、ACCESS_DENIED表示拒绝、ACCESS_ABSTAIN表示弃权 1 public interface AccessDecisionVoterS {2 int ACCESS_GRANTED 1;//表示同意3 int ACCESS_ABSTAIN 0;//表示弃权4 int ACCESS_DENIED -1;//表示拒绝5 ......6 } 那么什么情况下投票结果result为1呢
这里需要研究一下投票器接口AccessDecisionVoter该接口的实现如下图所示 这里简单介绍两个常用的
1. RoleVoter:这是用来判断url请求是否具备接口需要的角色这种主要用于使用注解Secured处理的权限 2. PreInvocationAuthorizationAdviceVoter针对类似注解PreAuthorize(hasAuthority(sys:user:add) AND hasAuthority(sys:user:edit))处理的权限 到这一步代码就开始难懂了这部分封装地过于复杂总体的逻辑是将用户信息所具有的权限与该接口的权限表达式做匹配若能匹配成功返回true在三目运算符中
allowed ? ACCESS_GRANTED : ACCESS_DENIED就会返回ACCESS_GRANTED 即表示通过这样返回给result的值就为1了。 到此为止本文就结束了笔者仍存在不足之处欢迎各位读者能够给予珍贵的反馈也算是对笔者写作的一种鼓励。