• spring security 用户授权/访问控制


    目录

    1、web 授权

    (1)访问控制的url匹配

    (2)基于权限的访问控制

    (3)基于角色的访问控制

    (3)基于表达式的访问控制

    2、方法授权

    (1)JSR-250注解

    (2)@Secured注解

    (3)支持表达式的注解


    授权的方式包括 web 授权和方法授权,web授权是通过 url 拦截进行授权,方法授权是通过方法拦截进行授权。他们都会调用 accessDecisionManager 进行授权决策,若为web授权则拦截器为FilterSecurityInterceptor;若为方法授权则拦截器为MethodSecurityInterceptor。如果同时通过web 授权和方法授权则先执行web授权,再执行方法授权,最后决策通过,则允许访问资源,否则将禁止访问。

    1、web 授权

    Spring Security 可以通过 http.authorizeRequests() 对web请求进行授权保护 ,Spring Security使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问。

    1. protected void configure(HttpSecurity http) throws Exception {
    2. http.formLogin();//表单提交
    3. // 授权 -> 认证拦截
    4. http.authorizeRequests()
    5. .antMatchers(
    6. "/login.html",
    7. "/error.html",
    8. "/main.html",
    9. "/admin/**")
    10. .permitAll() // 不需要认证
    11. .anyRequest() // 所有请求都必须认证
    12. .authenticated();
    13. http.csrf().disable(); //关闭csrf防护
    14. }

    (1)访问控制的url匹配

    在配置类中http.authorizeRequests() 主要是对url进行控制。配置顺序会影响之后授权的效果,越是具体的应该放在前面,越是笼统的应该放到后面。

    anyRequest(),表示匹配所有的请求。一般情况下此方法都会使用,设置全部内容都需要进行认证,会放在最后。

    1. http.authorizeRequests()
    2. .anyRequest() // 所有请求都必须认证
    3. .authenticated();

    antMatchers() ,方法定义如下:

    1. public C antMatchers(String... antPatterns) {
    2. return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
    3. }

    参数是不定项参数,每个参数是一个 ant 表达式,用于匹配 URL规则。ANT通配符有三种:

    通配符说明
    ?匹配任何单字符
    *匹配0或者任意数量的字符
    **匹配0或者更多的目录

    在实际项目中经常需要放行所有静态资源:

    1. // 放行js和css 目录下所有的文件
    2. .antMatchers("/js/**","/css/**").permitAll()
    3. // 只要是.js 文件都放行
    4. .antMatchers("/**/*.js").permitAll()

    regexMatchers(),使用正则表达式进行匹配。

    1. //所有以.js 结尾的文件都被放行
    2. .regexMatchers( ".+[.]js").permitAll()

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

    1. .antMatchers(HttpMethod.POST,"/admin/demo").permitAll()
    2. .regexMatchers(HttpMethod.GET,".+[.]jpg").permitAll()

    mvcMatchers(),适用于配置了 servletPath 的情况。 servletPath 就是所有的 URL 的统一前缀。在 SpringBoot 整合SpringMVC 的项目中可以在application.properties 中添加下面内容设置ServletPath。

    spring.mvc.servlet.path=/web

    在 Spring Security 的配置类中配置 .servletPath() 是 mvcMatchers()返回值特有的方法,antMatchers()和 regexMatchers() 没有这个方法。在 servletPath() 中配置了 servletPath 后,mvcMatchers()直接写 SpringMVC 中@RequestMapping()中设置的路径即可。

    .mvcMatchers("/admin/demo").servletPath("/web").permitAll()

    如果不习惯使用 mvcMatchers() 也可以使用 antMatchers(),下面代码和上面代码是等效的:

    .antMatchers("/web/admin/demo").permitAll()

    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 authority) 方法,拥有指定权限(authority)的用户可访问。
    • #hasAuthority(String... authorities) 方法,拥有指定任一权限(authority)的用户可访问。
    • #access(String attribute) 方法,当 Spring EL 表达式的执行结果为 true 时,可以访问。

    (2)基于权限的访问控制

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

    hasAuthority(String),判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建 User 对象时指定的。权限名称对大小写敏感

    1. //其中,admin,user就是用户的权限
    2. return new User(
    3. "swadian",
    4. pw,
    5. AuthorityUtils.commaSeparatedStringToAuthorityList("admin,user"));

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

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

    如果无权限访问,则会报403错误

    自定义403处理方案

    Spring Security 支持自定义权限受限处理,需要实现 AccessDeniedHandler 接口

    1. import org.springframework.security.access.AccessDeniedException;
    2. import org.springframework.security.web.access.AccessDeniedHandler;
    3. import javax.servlet.http.HttpServletRequest;
    4. import javax.servlet.http.HttpServletResponse;
    5. import java.io.IOException;
    6. import java.io.PrintWriter;
    7. public class MyAccessDeniedHandler implements AccessDeniedHandler {
    8. @Override
    9. public void handle(HttpServletRequest request, HttpServletResponse response,
    10. AccessDeniedException accessDeniedException) throws IOException {
    11. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
    12. response.setHeader("Content-Type", "application/json;charset=utf-8");
    13. PrintWriter out = response.getWriter();
    14. out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
    15. out.flush();
    16. out.close();
    17. }
    18. }

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

    1. @Override
    2. protected void configure(HttpSecurity http) throws Exception {
    3. http.formLogin();//表单提交
    4. // 授权 -> 认证拦截
    5. http.authorizeRequests()
    6. .antMatchers("/login.html", "/error.html", "/main.html").permitAll()
    7. .antMatchers("/admin/test").hasAnyAuthority("admin")
    8. .anyRequest().authenticated();// 所有请求都必须认证
    9. // 自定义403处理方案
    10. http.exceptionHandling()
    11. .accessDeniedHandler(new MyAccessDeniedHandler());
    12. http.csrf().disable(); //关闭csrf防护
    13. }

    hasAnyAuthority(String ...),如果用户具备给定权限中的某一个,就允许访问。

    .antMatchers("/admin/demo").hasAnyAuthority("admin","System")

    (3)基于角色的访问控制

    hasRole(String),如果用户具备给定角色就允许访问,否则出现 403。参数取值来源于自定义登录逻辑 UserDetailsService 实现类中创建 User 对象时给 User 赋予的授权。 在给用户赋予角色时角色需要以:ROLE_ 开头 ,后面添加角色名称。例如:ROLE_admin ,其中 admin是角色名,ROLE_ 是固定的字符开头。

    1. return new User(
    2. "swadian",
    3. pw,
    4. AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin"));//给用户赋予admin角色

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

    .antMatchers("/admin/test").hasRole("admin")

    hasAnyRole(String ...),如果用户具备给定角色的任意一个,就允许被访问 。

    hasIpAddress(String),如果请求是指定的 IP 就运行访问。 可以通过 request.getRemoteAddr() 获取 ip 地址。需要注意的是在本机进行测试时 localhost 和 127.0.0.1 输出的 ip地址是不一样的。

    1. // @Autowired
    2. // protected HttpServletRequest request;
    3. // String remoteAddr = request.getRemoteAddr();
    4. // localhost --> getRemoteAddr: 0:0:0:0:0:0:0:1
    5. .antMatchers("/admin/test").hasIpAddress("127.0.0.1")

    (3)基于表达式的访问控制

    access(表达式),以上的登录用户权限判断实际上底层实现都是调用access(表达式)

    Spring Security Reference

    表达式根对象的基类是SecurityExpressionRoot,提供了一些在web和方法安全性中都可用的通用表达式。

    可以通过 access() 实现和之前学习的权限控制完成相同的功能。

    1. .antMatchers("/user/login","/login.html").access("permitAll")
    2. .antMatchers("/admin/test").access("hasAuthority('System')")

    2、方法授权

    基于注解的访问控制

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

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

    (1)JSR-250注解

    @RolesAllowed

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

    1. @GetMapping("/test")
    2. @RolesAllowed({"ROLE_USER", "ROLE_ADMIN"})
    3. public String test() {
    4. return "Spring Security Test";
    5. }

    @PermitAll // 使用有坑

    允许所有的角色进行访问,@PermitAll可以标注在方法上也可以标注在类上。//方法优先于类

    1. 当@PermitAll标注在类上,@RolesAllowed标注在方法上,@RolesAllowed将覆盖@PermitAll,需要@RolesAllowed对应的角色才能访问方法。
    2. 当@RolesAllowed标注在类上,@PermitAll标注在方法上,则对应的方法不进行权限控制。
    3. 当在类和方法上同时使用了@PermitAll和@RolesAllowed,先定义注解的将发生作用(实际应用中不应该有这样的定义)。

    注意:@PermitAll 注解在存在java config 配置了授权模式的情况下(存在: http.authorizeRequests().anyRequest().authenticated())同样需要认证后再访问?// 注解失效

    因为 spring security 认证会先经过 FilterSecurityInterceptor 过滤器,利用匿名的认证用户进行投票决策,此时 vote 返回-1(因为没有匹配到当前url,只能匹配authenticated()),默认AffirmativeBased 决策下就会直接抛出 AccessDeniedException ,跳转到认证界面。此时就不会进入到 MethodSecurityInterceptor 的判断逻辑,所以必须认证之后才行。

    因此 @PermitAll 注解正常使用需要不被FilterSecurityInterceptor拦截,也就不能在 WebSecurityConfig 中配置 http.authorizeRequests().anyRequest().authenticated()

    @DenyAll

    和PermitAll相反的,表示所有角色禁止访问。@DenyAll只能定义在方法上。

    有人可能会有疑问,使用@DenyAll标注的方法无论什么权限都不能访问,那还定义它的意义何在呢?@DenyAll定义的方法只是在权限控制中不能访问,但脱离了权限控制还是可以访问的。

    注解的开启

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

    1. @Configuration // 标记为注解类
    2. @EnableGlobalMethodSecurity(jsr250Enabled = true)
    3. public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
    4. }

    (2)@Secured注解

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

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

    1. @Configuration // 标记为注解类
    2. @EnableGlobalMethodSecurity(securedEnabled = true)
    3. public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
    4. }
    1. @GetMapping("/test")
    2. @Secured("ROLE_ADMIN")
    3. public String test() {
    4. return "Spring Security Test";
    5. }

    (3)支持表达式的注解

    Spring Security 定义了四个支持使用表达式的注解,分别是@PreAuthorize、@PostAuthorize、

    @PreFilter 和 @PostFilter。其中前两者可以用来在方法调用前或者调用后进行权限检查,后两者可以用来对集合类型的参数或者返回值进行过滤。

    使用 @PreAuthorize 和 @PostAuthorize 进行访问控制

    1. @Configuration // 标记为注解类
    2. @EnableGlobalMethodSecurity(prePostEnabled = true)
    3. public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
    4. }

    @PreAuthorize 可以用来控制一个方法是否能够被调用,执行之前先判断权限,大多情况下都是使用这个注解。

    1. @GetMapping("/test")
    2. // @PreAuthorize("hasRole('ROLE_ADMIN')")
    3. // @PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
    4. // @PreAuthorize("#id<10") //限制只能查询Id小于10的用户
    5. @PreAuthorize("principal.username.equals('admin')") //限制只能用户名称为admin的用户
    6. public String test() {
    7. return "Spring Security Test";
    8. }

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

    1. // 在方法find()调用完成后进行权限检查,如果返回值的id是偶数则表示校验通过,
    2. // 否则表示校验失败,抛出AccessDeniedException
    3. @PostAuthorize("returnObject.id%2==0")
    4. public User find(int id) {
    5. User user = new User();
    6. user.setId(id);
    7. return user;
    8. }

    使用@PreFilter和@PostFilter进行过滤

    使用 @PreFilter 和 @PostFilter 可以对集合类型的参数或返回值进行过滤。使用@PreFilter和@PostFilter,Spring Security将移除对应表达式的结果为 false 的元素。

    1. @GetMapping("/user")
    2. @PostFilter("filterObject.id %2 == 0") //只会输出id为偶数的数据
    3. public List<TbUser> findAll() {
    4. List<TbUser> userList = new ArrayList<>();
    5. TbUser user;
    6. for (int i = 0; i < 10; i++) {
    7. user = new TbUser();
    8. user.setId(i);
    9. userList.add(user);
    10. }
    11. return userList;
    12. }

    @PreFilter 对输入参数进行过滤

    1. @PostMapping("/delete")
    2. @PreFilter(filterTarget = "ids", value = "filterObject %2 == 0")
    3. public void delete(List ids, List usernames) {
    4. }

    至此,spring security 用户授权方法介绍完毕,更详细内容请参照官方文档。

  • 相关阅读:
    网络安全进阶学习第十八课——业务逻辑漏洞(附录:不同行业业务逻辑的漏洞)
    数字ic设计——UART
    Modbus笔记
    超像素分割 & SLIC算法 & 使用示例
    CSS_背景属性
    springboot和vue:十二、VueRouter(动态路由)+导航守卫
    预训练相关知识
    大龄转行当程序员:只能选择小众技术,避免与年轻人竞争?
    C++中的this指针
    内存卡中毒了格式化能解决吗?这样清除病毒更有效
  • 原文地址:https://blog.csdn.net/swadian2008/article/details/126594147