SpringSecurity原理剖析及其实战(五)

11 篇文章 1 订阅
5 篇文章 0 订阅

1、用户授权(访问控制)

    • 什么是授权?
      授权的方式常分为两种,web授权和方法授权,web授权是通过url拦截进行授权,方法授权是通过方法拦截进行授权。他们都会调用accessDecisionManager进行授权决策,若为web授权则拦截器为FilterSecurityInterceptor;若为方法授权则拦截器为MethodSecurityInterceptor。如果同时通过web授权和方法授权则先执行web授权,再执行方法授权,最后决策通过,则允许访问资源,否则将禁止访问。
      在这里插入图片描述
    • web授权
      Spring Security可以通过http.authorizeRequests() 对web请求进行授权保护 Spring Security使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问。
http.authorizeRequests() //设置哪些路径可以直接访问,不需要认证
.antMatchers("/user/login","/login.html").permitAll() .anyRequest().authenticated(); //需要认证才能访问
    • 访问控制的url(这种现实场景比较少用)
      在配置类中http.authorizeRequests() 主要是对url进行控制。配置顺序会影响之后授权的效果,越是具 体的应该放在前面,越是笼统的应该放到后面。
      • anyRequest()
        表示匹配所有的请求。一般情况下此方法都会使用,设置全部内容都需要进行认证,会放在最后。
      • antMatchers()
        参数是不定向参数,每个参数是一个 ant 表达式,用于匹配 URL规则。
        ANT通配符有三种:
通配符说明
匹配任何单字符
*匹配0或者任意数量的字符
**匹配0或者更多的目录
      • regexMatchers()
        使用正则表达式进行匹配。
//所有以.js 结尾的文件都被放行 
.regexMatchers( ".+[.]js").permitAll()

无论是 antMatchers() 还是 regexMatchers() 都具有两个参数的方法,其中第一个参数都是 HttpMethod ,表示请求方式,当设置了 HttpMethod 后表示只有设定的特定的请求方式才执行对应的 权限设置。

      • mvcMatchers()
        适用于配置了 servletPath 的情况。 servletPath 就是所有的 URL 的统一前缀。在 SpringBoot 整合 SpringMVC 的项目中可以在application.properties 中添加下面内容设置 ServletPath。
      • RequestMatcher接口
        RequestMatcher 是 Spring Security Web 的一个概念模型接口,用于抽象建模对
        HttpServletRequest 请求的匹配器这一概念。 Spring Security 内置提供了一些 RequestMatcher
        实现类:
        在这里插入图片描述
        内置的访问控制
        • 【常用】#permitAll() 方法,所有用户可访问。
        • 【常用】#denyAll() 方法,所有用户不可访问。
        • 【常用】 #authenticated() 方法,登录用户可访问。
        • #anonymous() 方法,无需登录,即匿名用户可访问。
        • #rememberMe() 方法,通过 remember me登录的用户可访问。
        • #fullyAuthenticated() 方法,非 remember me 登录的用户可访问。
        • #hasIpAddress(String ipaddressExpression) 方法,来自指定 IP 表达式的用户可访问。
        • 【常用】 #hasRole(String role) 方法, 拥有指定角色的用户可访问,角色将被增加 “ROLE_” 前缀。
        • 【常用】 #hasAnyRole(String… roles) 方法,拥有指定任一角色的用户可访问。
        • 【常用】 方法,拥有指定权限( )的用户可访 问。
        • 【常用】 #hasAuthority(String… authorities) 方法,拥有指定任一权限( authority )的用 户可访问。
        • 【最牛】 #access(String attribute) 方法,当 Spring EL 表达式的执行结果为 true 时,可以 访问。

2、基于权限的访问控制

除了之前讲解的内置权限控制。Spring Security 中还支持很多其他权限控制。这些方法一般都用于用户 已经被认证后,判断用户是否具有特定的要求。

      • hasAuthority(String)
        判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建 User 对象时指定的。权限名称 大小写敏感
return new User("fox", pw, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,user"));//admin,user就 是用户的权限

在配置类中通过 hasAuthority(“admin”)设置具有 admin 权限时才能访问。

.antMatchers("/admin/demo").hasAuthority("admin")

在这里插入图片描述

      • hasAnyAuthority(String …)
        如果用户具备给定权限中某一个,就允许访问。
.antMatchers("/admin/demo").hasAnyAuthority("admin","System")
    • 基于角色的访问控制
      • hasRole(String)
        如果用户具备给定角色就允许访问,否则出现 403。参数取值来源于自定义登录逻辑 UserDetailsService 实现类中创建 User 对象时给 User 赋予的授权。 在给用户赋予角色时角色需要以:
        ROLE_ 开头 ,后面添加角色名称。例如:ROLE_admin 其中 admin是角 色名, ROLE_ 是固定的字符开 头。
return new User("fox", pw, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin,user"));//给用户赋 予admin角色

使用 hasRole()时参数也只写admin 即可,否则启动报错。

.antMatchers("/admin/demo").hasRole("admin")
      • hasAnyRole(String …)
        如果用户具备给定角色的任意一个,就允许被访问 。
      • hasIpAddress(String)
        如果请求是指定的 IP 就运行访问。 可以通过 request.getRemoteAddr() 获取 ip 地址。需要注意的是在 本机进行测试时 localhost 和 127.0.0.1 输出的 ip地址是不一样的。
//  localhost --> getRemoteAddr:  0:0:0:0:0:0:0:1
.antMatchers("/admin/demo").hasIpAddress("127.0.0.1")
    • 自定义403处理方案
      使用 Spring Security 时经常会看见 403(无权限)。Spring Security 支持自定义权限受限处理,需要
      实现 AccessDeniedHandler接口
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException,
ServletException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setHeader("Content-Type", "application/json;charset=utf-8"); PrintWriter out = response.getWriter(); out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}"); out.flush();
out.close();
} }

在配置类中设置访问受限后交个MyAccessDeniedHandler处理

http.exceptionHandling()
     .accessDeniedHandler(new MyAccessDeniedHandler());

3、基于表达式的访问控制

    • access(表达式)
      之前学习的登录用户权限判断实际上底层实现都是调用access(表达式)
      相关链接

4、方法授权

    • 基于注解的访问控制

Spring Security在方法的权限控制上支持三种类型的注解,JSR-250注解、@Secured注解和支持表达式的注解。这三种注解默认都是没有启用的,需要通过@EnableGlobalMethodSecurity来进行启用。

这些注解可以写到 Service 接口或方法上,也可以写到 Controller或 Controller 的方法上。通常情况下都是写在控制器方法上的,控制接口URL是否允许被访问。

    • JSR-250注解
      • @RolesAllowed

表示访问对应方法时所应该具有的角色。其可以标注在类上,也可以标注在方法上,当标注在类上时表
示其中所有方法的执行都需要对应的角色,当标注在方法上表示执行该方法时所需要的角色,当方法和
类上都使用了@RolesAllowed进行标注,则方法上的@RolesAllowed将覆盖类上的@RolesAllowed,即
方法上@RolesAllowed将对当前方法起作用。@RolesAllowed的值是由角色名称组成的数组。

      • @PermitAll

表示允许所有的角色进行访问,也就是说不进行权限控制。@PermitAll可以标注在方法上也可以标注在类上,当标注在方法上时则只对对应方法不进行权限控制,而标注在类上时表示对类里面所有的方法都不进行权限控制。
(1)当@PermitAll标注在类上,而@RolesAllowed标注在方法上时则按照
@RolesAllowed将覆盖@PermitAll,即需要@RolesAllowed对应的角色才能访问。
(2)当@RolesAllowed标注在类上,而@PermitAll标注在方法上时则对应的方法也是不进行权限控制 的。
(3)当在类和方法上同时使用了@PermitAll和@RolesAllowed时先定义的将发生作用(这个没多大的实际意义,实际应用中不会有这样的定义)。

      • @DenyAll

是和PermitAll相反的,表示无论什么角色都不能访问。@DenyAll只能定义在方法上。你可能会有疑问
使用@DenyAll标注的方法无论拥有什么权限都不能访问,那还定义它干啥呢?使用@DenyAll定义的方法只是在我们的权限控制中不能访问,脱离了权限控制还是可以访问的。

开启注解 在启动类或者在配置类上添加 @EnableGlobalMethodSecurity(jsr250Enabled = true)

@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

在controller方法上添加@RolesAllowed注解

@RolesAllowed({"ROLE_USER", "ROLE_ADMIN"})
//@PermitAll
@GetMapping("/demo")
public String demo() {
    return "spring security demo";
}

修改配置类

@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //表单提交
.loginPage("/login.html") //自定义登录页面 .loginProcessingUrl("/user/login") //登录访问路径,必须和表单提交接口一样 .successHandler(new MyAuthenticationSuccessHandler("/main.html")) .failureHandler(new MyAuthenticationFailureHandler("/error.html")) //授权
.and().authorizeRequests()
//设置哪些路径可以直接访问,不需要认证 .antMatchers("/user/login","/login.html").permitAll() .anyRequest().authenticated() //需要认证
.and().csrf().disable(); //关闭csrf防护
}
      • @Secured注解

@Secured是由Spring Security定义的用来支持方法权限控制的注解。它的使用也是需要启用对应的支
持才会生效的。@Secured 是专门用于判断是否具有角色的,能写在方法或类上。参数要以 ROLE_开 头。

开启注解 在启动类或者在配置类上添加 @EnableGlobalMethodSecurity(securedEnabled = true)

@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

在controller方法上添加@Secured 注解

@Secured("ROLE_ADMIN")
@GetMapping("/demo")
public String demo() {
    return "spring security demo";
}
      • 支持表达式的注解

Spring Security中定义了四个支持使用表达式的注解,分别是@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter。其中前两者可以用来在方法调用前或者调用后进行权限检查,后两者可以用来对集合类型的参数或者返回值进行过滤。

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
      • 使用@PreAuthorize和@PostAuthorize进行访问控制
        @PreAuthorize可以用来控制一个方法是否能够被调用,执行之前先判断权限,大多情况下都是使用这个注解。
//@PreAuthorize("hasRole('ROLE_ADMIN')") //@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')") //限制只能查询Id小于10的用户
@PreAuthorize("#id<10")
@RequestMapping("/findById")
public User findById(long id) {
    User user = new User();
    user.setId(id);
    return user;
}
// 限制只能查询自己的信息 @PreAuthorize("principal.username.equals(#username)") @RequestMapping("/findByName")
public User findByName(String username) {
    User user = new User();
    user.setUsername(username);
    return user;
}
//限制只能新增用户名称为abc的用户 @PreAuthorize("#user.username.equals('abc')") @RequestMapping("/add")
public User add(User user) {
    return user;
}

@PostAuthorize可以在方法调用完之后进行权限检查

// 在方法find()调用完成后进行权限检查,如果返回值的id是偶数则表示校验通过,否则表示校验失败,将 抛出AccessDeniedException
@PostAuthorize("returnObject.id%2==0")
public User find(int id) {
    User user = new User();
    user.setId(id);
    return user;
}
      • 使用@PreFilter和@PostFilter进行过滤
@PostFilter("filterObject.id%2==0")
public List<User> findAll() {
    List<User> userList = new ArrayList<User>();
    User user;
    for (int i=0; i<10; i++) {user = new User();
        user.setId(i);
        userList.add(user);
}
    return userList;
}
@PreFilter(filterTarget="ids", value="filterObject%2==0")
public void delete(List<Integer> ids, List<String> usernames) {
}

5 、授权原理

重写 #configure(HttpSecurity http) 方法,主要配置 URL 的权限控制

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
// 配置请求地址的权限 
.authorizeRequests()
.antMatchers("/test/echo").permitAll() // 所有用户可访问 .antMatchers("/test/admin").hasRole("ADMIN") // 需要 ADMIN 角色 .antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')") //需要 NORMAL 角色。
// 任何请求,访问的用户都需要经过认证
.anyRequest().authenticated()
.and()
// 设置 Form 表单登录
//自定义登录页面,可以通过 #loginPage(String loginPage) 设置 
.formLogin()
//.loginPage("/login") // 登录 URL 地址
 .permitAll() // 所有用户可访问
.and()
// 配置退出相关 .logout()
.logoutUrl("/logout") // 退出 URL 地址 
.permitAll(); // 所有用户可访问
}

调用 HttpSecurity#authorizeRequests() 方法,开始配置 URL 的权限控制

修改 WebSecurityConfig配置类,增加 @EnableGlobalMethodSecurity 注解,开启对 Spring Security 注解的方法,进行权限验证。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@RestController
@RequestMapping("/demo")
public class DemoController {
@PermitAll
    @GetMapping("/echo")
    public String demo() {
return "示例返回"; }
    @GetMapping("/home")
    public String home() {
return "我是首页"; }
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/admin")
    public String admin() {
return "我是管理员"; }
    @PreAuthorize("hasRole('ROLE_NORMAL')")
    @GetMapping("/normal")
    public String normal() {
return "我是普通用户"; }
}

6、授权流程

    • 基于Filter
      在这里插入图片描述
  1. 拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中的 FilterSecurityInterceptor 的子 类拦截。
  2. 获取资源访问策略,FilterSecurityInterceptor会从 SecurityMetadataSource 的子类 DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限Collection 。 SecurityMetadataSource其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访 问规则
  3. 最后,FilterSecurityInterceptor会调用 AccessDecisionManager 进行授权决策,若决策通过,则 允许访问资 源,否则将禁止访问。
    • 基于AOP
//MethodSecurityInterceptor#invoke
public Object invoke(MethodInvocation mi) throws Throwable {
    InterceptorStatusToken token = super.beforeInvocation(mi);
    Object result;
    try {
        result = mi.proceed();
    }
    finally {
        super.finallyInvocation(token);
}
    return super.afterInvocation(token, result);
}
    • 相关接口
      AccessDecisionManager
      AccessDecisionManager采用投票的方式来确定是否能够访问受保护资源。 AccessDecisionManager 中包含的一系列AccessDecisionVoter将会被用来对Authentication是否有权访问受保护对象进行投票, AccessDecisionManager根据投票结果,做出最终决策 。
public interface AccessDecisionManager {
    // ~ Methods
    //
================================================================================
========================
/**
* 用来鉴定当前用户是否有访问对应受保护资源的权限
* authentication:要访问资源的访问者的身份
* object:要访问的受保护资源,web请求对应FilterInvocation
* configAttributes:是受保护资源的访问策略,通过SecurityMetadataSource获取 */
    void decide(Authentication authentication, Object object,
            Collection<ConfigAttribute> configAttributes) throws
AccessDeniedException,
            InsufficientAuthenticationException;
    boolean supports(ConfigAttribute attribute);
    boolean supports(Class<?> clazz);
}

在这里插入图片描述

      • AffirmativeBased

AffirmativeBased的逻辑是:
(1)只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;
(2)如果全部弃权也表示通过;
(3)如果没有一个人投赞成票,但是有人投反对票,则将抛AccessDeniedException。 Spring security默认使用的是AffirmativeBased。

      • ConsensusBased

ConsensusBased的逻辑是:
(1)如果赞成票多于反对票则表示通过。
(2)反过来,如果反对票多于赞成票则将抛出AccessDeniedException。
(3)如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions的值为true,则表 示通过,否则将抛出异常
AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认为true。
(4)如果所有的AccessDecisionVoter都弃权了,则将视参allowIfAllAbstainDecisions的值而定,如果该值为true则表示通过,否则将抛出异常AccessDeniedException。参数allowIfAllAbstainDecisions的值默认为false。

      • UnanimousBased

UnanimousBased的逻辑与另外两种实现有点不一样,另外两种会一次性把受保护对象的配置属性全部 传递给AccessDecisionVoter进行投票,而UnanimousBased会一次只传递一个ConfigAttribute给AccessDecisionVoter进行投票。这也就意味着如果我们的AccessDecisionVoter的逻辑是只要传递进来 的ConfigAttribute中有一个能够匹配则投赞成票,但是放到UnanimousBased中其投票结果就不一定 是赞成了。
UnanimousBased的逻辑具体来说是这样的:
(1)如果受保护对象配置的某一个 ConfigAttribute被任意的AccessDecisionVoter反对了,则将抛出 AccessDeniedException。
(2)如 果没有反对票,但是有赞成票,则表示通过。
(3)如果全部弃权了,则将视参数allowIfAllAbstainDecisions的值而定,true则通过,false则抛出 AccessDeniedException。

      • AccessDecisionVoter
public interface AccessDecisionVoter<S> {
	int ACCESS_GRANTED = 1; //同意 int ACCESS_ABSTAIN = 0; //弃权 int ACCESS_DENIED = -1; //拒绝
	boolean supports(ConfigAttribute attribute);
	boolean supports(Class<?> clazz);
// 返回结果是AccessDecisionVoter中定义的三个常量之一 
	int vote(Authentication authentication, S object,
		Collection<ConfigAttribute> attributes);
}

在这里插入图片描述

      • MethodSecurityInterceptor
        Spring Security提供了两类AbstractSecurityInterceptor,基于AOP Alliance的 MethodSecurityInterceptor,和基于Aspectj继承自MethodSecurityInterceptor的 AspectJMethodSecurityInterceptor
//MethodSecurityInterceptor#invoke
public Object invoke(MethodInvocation mi) throws Throwable {
    InterceptorStatusToken token = super.beforeInvocation(mi);
    Object result;
    try {
        result = mi.proceed();
    }
    finally {
        super.finallyInvocation(token);
}
    return super.afterInvocation(token, result);
}

在这里插入图片描述

到这里spring security算是完结了,下篇开始就是spring security oauth2的实战学习

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值