spring security权限校验

Spring Security概述

1.1 Spring Security介绍

Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架。

官网地址: https://projects.spring.io/spring-security/

Spring Security 为基于J2EE企业应用软件提供了全面安全服务。

特别是使用领先的J2EE解决方案-Spring框架开发的企业软件项目。人们使用Spring Security有很多种原因,不过通常吸引他们的是在J2EE Servlet规范或EJB规范中找不到典型企业应用场景的解决方案。 特别要指出的是他们不能再

WAR 或 EAR 级别进行移植。这样,如果你更换服务器环境,就要,在新的目标环境进行大量的工作,对你的应用

系统进行重新配 置安全。使用Spring Security 解决了这些问题,也为你提供很多有用的,完全可以指定的其他安

全特性。 安全包括两个主要操作。

本质:

Spring Security 采用 AOP,以及基于 Filter过滤器实现的安全认证框架。它提供了完善的认证机构和授权功能,而在做系统的时候,一般做的第一个模块就是认证与授权模块,因为这是一个系统的入口,也是一个系统最重要最基础的一环,在认证与授权服务设计搭建好了之后,剩下的模块才得以安全访问。

小结:

  1. springSecurity基于spring构建的安全认证服务框架
  2. springSecurity基于Filter实现的安全认证服务框架(必须在web项目中).
  3. springSecurity基于aop实现认证和授权服务的(aop底层是动态代理)

1.2 登录认证(Authentication)和访问授权(Authorization)

  • 什么是登录认证

    指的获取用户输入的账号和密码信息,校验是否正确,这个过程就是认证过程.

  • 什么是访问授权

    指的登录认证通过后, 根据用户的账号信息获取该用户所匹配的资源数据, 让用户访问与之匹配的资源.

    比如: 资源数据指的用户访问的页面,或者用户访问controller里面的请求路径,执行controller里面的方法

  • **“登录认证”**指的是为用户建立一个他所声明的主体,主题一般式指用户,设备或可以在你系 统中执行动作的其他系统。

    1、基于表单的认证(Cookie & Session):基于表单的认证并不是在 HTTP 协议中定义的,而是服务器自己实现的认证方式,安全程度取决于实现程度。一般用 Cookie 来管理 Session 会话,是最常用的认证方式之一。它的安全程度取决于服务器的实现程度,客户端在Cookie中携带认证信息,服务器解析并返回结果。

    总结:

    1. 首次访问服务器端, 将用户信息 提交服务器端, 在服务器端对用户提交的信息进行认证,

      如果认证通过将认证过的信息存到cookie里面,同时向cookie响应到浏览器端

      比如: cookie里面存到信息: pass=“yes”

    2. 下次再访问时, 提交cookie到服务器端, 在服务器端获取cookie里面的信息, 判断是否已经认证,

    比如: 在服务器端获取cookie里面的信息: pass="yes",说明之前认证过了,不在认证
    

    2、基于JWT(Json Web Token)的认证:App和服务端常用的认证方式,用户ID和密码传输到服务器上验证,服务器验证通过以后生成加密的JWT Token返回给客户端,客户端再发起请求时携带返回的Token进行认证。(多了个防篡改)

    总结: 基于 JWT 令牌认证(三部分数据)认证过程,进行加密处理, 将认证后的数据以json格式形式保存到token.

    ​ 底层也是基于cookie实现的.

    1. 用户首次访问服务器端, 提交用户信息, 在服务器端进行认证, 认证通过过,将认证的信息以json格式保存到token

      底层: 将token保存到cookie里面, 将cookie响应到客户端.

    2. 下次再次访问服务器端时, 将客户端保存的cookie提交到服务器端, 在服务器端获取cookie, 解析里面的 token.

      如果token里面保存了之前的认证信息, 可以不再认证了

      比如: 基于jwt进行登录认证,可以对认证数据进行加密, 可以对认证数据设置过期时间.

    3、Http Basic 认证:最早的 Http 认证方式,用户 ID 和密码以分号连接,经过 Base64 编码后存储到 Authorization 字段,发送到服务端进行认证 ;用户 ID/密码 以明文形式暴露在网络上,安全性较差。(如果没有使用 SSL/TLS 这样的传输层安全的协议,那么以明文传输的密钥和口令很容易被拦截)

    4、Http Digest 认证:在 HttpBasic 的基础上,进行了一些安全性的改造,用户ID, 密码 , 服务器/客户端随机数,域,请求信息,经过 MD5 加密后存储到 Authorization 字段,发送到服务端进行认证;密码经过 MD5 加密,安全性比 Basic 略高。

    5、其他认证方式(Oauth 认证,单点登陆,HMAC 认证):通过特定的加密字段和加密流程,对客户端和服务端的信息进行加密生成认证字段,放在 Authorization 或者是消息体里来实现客户信息的认证

    • Oauth认证: 基于第三方进行认证, 比如: 登录csdn可以使用第三方账号,比如: 微信登录.

    • 单点登录: 一次登录,到处通行(指的登录一个网站,可以访问与之相关的其它网站,这时在访问其它网站时,不在认证)

      比如: 比如登录淘宝以后, 再访问天猫,或者聚划算, 或者 阿里云 不用再登录.

      单点登录的实现方式:

      比如基于cas进行单点登录: client端---------------搭建的单点登录服务器.

      1. 第一次访问淘宝, 输入账号信息, 将账号信息提交到单点登录服务器 进行认证,认证通过以后, 将认证通过后的信息存到cookie里面.

      2. 下次再访问 淘宝时,提供cookie到单点登录服务器, 获取cookie.解析认证信息,如果之前认证过了 , 不再认证.

      3. 下次再访问天猫时, 如果能提交之前保存认证信息的cookie到单点登录服务器, 直接认证通过.

        如果不能提交之前保存认证信息的cookie, 必须再次认证.

  • “访问授权” 指的是一个用户能否在你的应用中执行某个操作,在到达授权判断之前,身份的主题已经由 身份验证

过程建立了。

这些概念是通用的,不是Spring Security特有的。在身份验证层面,Spring Security广泛支持各种身份验证模式,

这些验证模型绝大多数都由第三方提供,或则正在开发的有关标准机构提供的,

例如 Internet Engineering TaskForce.作为补充,

Spring Security 也提供了自己的一套验证功能。

Spring Security 目前支持认证一体化如下认证技术:

HTTP BASIC authentication headers (一个基于IEFT RFC 的标准)

HTTP Digest authentication headers (一个基于IEFT RFC 的标准)

HTTP X.509 client certifificate exchange(一个基于IEFT RFC 的标准)

LDAP (一个非常常见的跨平台认证需要做法,特别是在大环境)

Form-based authentication (提供简单用户接口的需求)

OpenID authentication Computer Associates Siteminder JA-SIG Central Authentication Service (CAS,这是一个流行的开源单点登录系统)

Transparent authentication context propagation for Remote Method Invocation and HttpInvoker (一个Spring远程调用协议)

总结:

  1. 认证 (Authentication): 你是谁。

    Authentication(认证) 是验证您的身份的凭据(例如用户名/用户ID和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。

  2. 授权 (Authorization): 你有权限干什么。

    Authorization(授权) 发生在 Authentication(认证) 之后。授权嘛,光看意思大家应该就明白,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如 admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。

    如图:在这里插入图片描述

1.3 认证流程

在这里插入图片描述

这里的 AuthenticationFilter 采用的是责任链设计模式(请求层层上报,直到有人解决为止),一个 web 请求会经过一条过滤器链,在经过过滤器链的过程中会完成认证与授权,如果中间发现这条请求未认证或者未授权,会根据被保护 API 的权限去抛出异常,然后由异常处理器去处理这些异常。

FilterchainProxy过滤链List如下图所示

在这里插入图片描述

注:这里 Security 提供两种过滤器类:
UsernamePasswordAuthenticationFilter 表示表单登陆过滤器 BasicAuthenticationFilter 表示 httpBasic 方式登陆过滤器

如上图,一个请求想要访问到 API 就会以从左到右的形式经过蓝线框框里面的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分负责异常处理,橙色部分则是负责授权。

不过上述的两个过滤器是 Spring Security 对 form 表单认证和 Basic 认证内置的两个 Filter.

1.4 认证核心组件(api)

  • SecurityContext:上下文对象,Authentication 对象会放在里面。
  • SecurityContextHolder:用于拿到上下文对象的静态工具类。
  • Authentication:认证接口,定义了认证对象的数据形式。
  • AuthenticationManager:用于校验 Authentication,返回一个认证完成后的 Authentication 对象
SecurityContext

上下文对象,认证后的数据就放在这里面,接口定义如下:

public interface SecurityContext extends Serializable {
 // 获取Authentication对象
 Authentication getAuthentication();

 // 放入Authentication对象
 void setAuthentication(Authentication authentication);
}
SecurityContextHolder

用于拿到上下文对象的静态工具类。

SecurityContextHolder 用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限… 这些都被保存在 SecurityContextHolder 中。SecurityContextHolder 默认使用 ThreadLocal 策略来存储认证信息。

public class SecurityContextHolder {

 public static void clearContext() {
  strategy.clearContext();
 }

 public static SecurityContext getContext() {
  return strategy.getContext();
 }

 public static void setContext(SecurityContext context) {
  strategy.setContext(context);
 }

}

可以说是 SecurityContext 的工具类,用于 get or set or clear SecurityContext,默认会把数据都存储到当前线程中。

从这个 SecurityContextHolder 取得 UserDetail 的实例:

public static String getLoginAccount() {
    // getPrincipal 返回值需要强转为 UserDetail 具体原因看下面的 Authentication那节
    return ((UserDetail) SecurityContextHolder
    .getContext()
    .getAuthentication() // 返回:Authentication
    .getPrincipal()) // 这里就是 Authentication 内部保存的 UserDetail 对象
    .getUsername();
}
Authentication

认证接口,定义了认证对象的数据形式。

public interface Authentication extends Principal, Serializable {

 Collection<? extends GrantedAuthority> getAuthorities();
 Object getCredentials();
 Object getDetails();

 // AuthenticationManager 实现通常会返回一个包含更丰富信息的 Authentication 作为供应用程序使用的主体。 
 // 许多身份验证提供程序将创建一个 UserDetails 对象作为主体
 // 所以如果 AuthenticationManager 使用的是 ProviderManager 则这里返回值需要强转为 UserDetails
 Object getPrincipal();

 boolean isAuthenticated();
 void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

这几个方法效果如下:

  • getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。
  • getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。
  • getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)。
  • getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails。
  • isAuthenticated: 获取当前 Authentication 是否已认证。
  • setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。

Authentication 只是定义了一种在 SpringSecurity 进行认证过的数据的数据形式应该是怎么样的,要有权限,要有密码,要有身份信息,要有额外信息。

AuthenticationManager ⭐

认证管理器很多,在这里不做过多介绍

public interface AuthenticationManager {
 // 认证方法
 Authentication authenticate(Authentication authentication)
   throws AuthenticationException;
}

AuthenticationManager (接口)是认证相关的核心接口,它定义了一个认证方法,它将一个未认证的 Authentication 传入,返回一个已认证的 Authentication。它的默认实现类是 ProviderManager

注:AuthenticationManager 有多个认证类 AuthenticationManager,ProviderManager ,AuthenticationProvider …

总结:

整合上面四个组件认证流程

将这四个部分,串联起来,构成 Spring Security 进行认证的流程:

1、先是一个请求带着身份信息进来,用户名和密码被过滤器获取到,封装成 Authentication,通常情况下是 UsernamePasswordAuthenticationToken 这个实现类。

2、这个 Authentication 经过 AuthenticationManager 的认证(身份管理器负责验证)认证成功后,AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication 实例。

3、SecurityContextHolder 安全上下文容器将上面填充了信息的 Authentication,通过 SecurityContextHolder.getContext().setAuthentication(…) 方法,设置到 SecurityContext 其中。

下面编写一个例子,具体的描述这个流程(不是源码, 只是模拟认证流程)

public class AuthenticationExample {
    private static final AuthenticationManager am = new SampleAuthenticationManager();

    public static void main(String[] args) throws Exception {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        while(true) {
            System.out.println("Please enter your username:");
            String name = in.readLine();
            System.out.println("Please enter your password:");
            String password = in.readLine();

            try {
                // 1、封装一个 UsernamePasswordAuthenticationToken 对象
                Authentication request = new UsernamePasswordAuthenticationToken(name, password);

                // 2、经过 AuthenticationManager 的认证,如果认证失败会抛出一个 AuthenticationException 错误
                Authentication result = am.authenticate(request);

                // 3、将这个认证过的 Authentication 填入 SecurityContext 里面
                SecurityContextHolder.getContext().setAuthentication(result);
                break;
            } catch(AuthenticationException e) {
                System.out.println("Authentication failed:" + e.getMessage());
            }
        }

        System.out.println("Successfully authenticated. Security context contains:\n" +
                SecurityContextHolder.getContext().getAuthentication());
    }
}

// 实现一个简单 AuthenticationManager 用于认证
class SampleAuthenticationManager implements AuthenticationManager {
    static final List<GrantedAuthority> AUTHORITIES = new ArrayList<>();

    static {
        AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
    }

    // 关键认证部分
    @Override
    public Authentication authenticate(Authentication auth) throws AuthenticationException {

        // getCredentials 返回的是密码,这里随便写了,直接用户名和密码一致就算登陆成功
        if (auth.getName().equals(auth.getCredentials())) {
            // 认证成功返回一个已经认证的 UsernamePasswordAuthenticationToken 的对象,并把这个用户的权限填入
            return new UsernamePasswordAuthenticationToken(auth.getName(),
                    auth.getCredentials(), AUTHORITIES);
        }
        throw new BadCredentialsException("Bad Credentials");
    }
}

1.5 授权流程

授权分析

认证成功之后会将当前登录用户信息保存到Authentication对象中,Authentication对象中有一个getAuthorities()方法,用来返回当前登录用户具备的权限信息,也就是当前用户具有权限信息。该方法的返回值为Collection<? extends GrantedAuthority>,当需要进行权限判断时,就会根据集合返回权限信息调用相应 方法进行判断。
在这里插入图片描述

那么问题来了,针对于这个返回值GrantedAuthority应该如何理解?是角色还是权限?

基于角色进行权限管理

一般情况下: 设计权限表:

user表-user_role中间表–role角色表-----role_permission中间表 --permission权限表

实现方式:

在认证通过以后, 根据用户信息查询该用户所拥有的角色, 将用户认证信息和角色信息设置到springSecurity提供的UserDetails(比较流行)

基于资源进行权限管理 -->权限字符串

R(Role Resources) B(base) A(access) C(controll)

我们针对于授权可以是基于角色权限管理和基于资源权限管理,从设计层面上来说,角色和权限是两个完全不同的东西:权限是一些具体操作,角色则是某些权限集合。如:READ_BOOK和ROLE_ADMIN是完全不同的。因此至于返回值是什么取决于你的业务设计情况:

基于角色权限设计就是:用户<>角色<>资源 三者关系 返回就是用户的角色(用的多)

基于资源权限设计就是: 用户<>权限<>资源 三者关系 返回就是用户的权限

基于角色和资源权限设计就是: 用户<>角色<>权限<==>资源 返回统称为用户的权限

为什么统称为权限,因为从代码层面角色和权限没有太大不同都是权限,特别是在Spring Security中。角色和权限处理方式基本上都是一样的。唯一区别SpringSecurity在很多时候会自动给角色添加一个**ROLE_**前缀,而权限则不会自动添加。

权限管理策略

可以访问系统中哪些资源(http url method)

Spring Security中提供的权限管理策略主要有两种类型:

  • 基于过滤器(URL)的权限管理(FilterSecurityInterceptor)
    基于过滤器的权限管理主要用来拦截HTTP请求,拦截下来之后,根据HTTP请求地址进行权限校验。
  • 基于AOP的权限管理(MethodSecurityInterceptor)
    基于AOP权限管理主要是用来处理方法级别的权限问题。当需要调用某一个方法时,通过AOP将操作拦截下来,然后判断用户是否具备相关的权限。
基于url的权限管理代码如下
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //创建内存数据源
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("ADMIN","USER").build());
        inMemoryUserDetailsManager.createUser(User.withUsername("lisi").password("{noop}123").roles("USER").build());
        inMemoryUserDetailsManager.createUser(User.withUsername("win7").password("{noop}123").authorities("READ_INFO").build());
        return inMemoryUserDetailsManager;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .mvcMatchers("/admin").hasRole("ADMIN")  //具有 admin角色
                .mvcMatchers("/user").hasRole("USER")   //具有user角色
                .mvcMatchers("/getInfo").hasAuthority("READ_INFO")  //具有read_info权限
                .antMatchers(HttpMethod.GET,"admin").hasRole("ADMIN")
//                .regexMatchers().hasRole()   //好处: 支持正则表达式
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf()
                .disable();
    }
}


权限表达式

在这里插入图片描述

在这里插入图片描述

基于方法的权限管理

基于方法的权限管理主要是通过AOP来实现的,Spring Security中通过MethodSecurityInterceptor来提供相关的实现。不同在于FilterSecurityInterceptor只是在请求之前进行前置处理,MethodSecurityInterceptor除了前置处理之外还可以进行后置处理。前置处理就是在请求之前判断是否具备相应的权限,后置处理则是对方法的执行结果进行二次过滤。前置处理和后置处理分别对应了不同的实现类。

@EnableGlobalMethodSecurity

EnableGlobalMethodSecurity该注解是用来开启权限注解,用法如下:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true,jsr250Enabled=true)
public class SecurityConfig extends WebsecurityConfigurerAdapter{

}

perPostEnabled:开启Spring Security提供的四个权限注解,

@PostAuthorize、@PostFilter、@PreAuthorize以及@PreFilter。
securedEnabled:开启Spring Security提供的@Secured注解支持,该注解不支持权限表达式
jsr250Enabled:开启JSR-250提供的注解,主要是@DenyAll、@PermitAll、@RolesAll同样这些注解也不支持权限表达式

以上注解含义如下:

  • @PostAuthorize: 在目标方法执行之后进行权限校验。
  • @PostFilter: 在目标方法执行之后对方法的返回结果进行过滤。
  • @PreAuthorize: 在目标方法执行之前进行权限校验。
  • @PreFilter: 在目标方法执行之前对方法参数进行过滤。
  • @secured: 访问目标方法必须具备相应的角色。
  • @DenyAll: 拒绝所有访问。
  • @PermitAll: 允许所有访问。
  • @RolesAllowed: 访问目标方法必须具备相应的角色。

这些机遇方法的权限管理相关的注解,一般来说只要设置prePostEnabled=true就够用了。

如图配置:
在这里插入图片描述

代码如下

@RestController
@RequestMapping("/hello")
public class AuthorizeMethodController {

    @PreAuthorize("hasRole('ADMIN') and authentication.name=='root'")
    @GetMapping
    public String hello(){
        return "hello";
    }
    @PreAuthorize("authentication.name ==#name")    //参数与认证的用户名一致才可以访问
    @GetMapping("/name")
    public String hello(String name){
        return "hello:" + name;
    }

    @PreFilter(value ="filterObject.id%2!=0",filterTarget = "users")   //filterTarget表示要过滤的参数,必须是数组、集合类型    filterObject是数组中的对象,如User。
    @PostMapping("/users")
    public void addUsers(@RequestBody List<User> users){
        System.out.println("users = "+ users);
    }

    @PostAuthorize("returnObject.id ==1")    //表示方法返回前处理
    @GetMapping("/userId")
    public User getUserById(Integer id){
        return new User(id,"zkt");
    }
    @PostFilter("filterObject.id%2==0")   //用来对方法返回值进行过滤
    @GetMapping("/lists")
    public List<User> getAll(){
        List<User> users = new ArrayList<>();
        for(int i = 0; i < 10; i++){
            users.add(new User(i,"zkt"+i));
        }
        return users;
    }
    @Secured({"ROLE_USER"}) //只能判断角色
    @GetMapping("/secured")
    public User getUserByUsername(){
        return new User(99,"secured");
    }

    @Secured({"ROLE_ADMIN","ROLE_USER"}) //具有其中一个即可
    @GetMapping("/username")
    public User getUserByUsername2(String username){
        return new User(99,username);
    }

    @PermitAll
    @GetMapping("/permitAll")
    public String permitAll(){
        return "PermitAll";
    }

    @DenyAll
    @GetMapping("/denyAll")
    public String denyAll(){
        return "DenyAll";
    }

    @RolesAllowed({"ROLE_ADMIN","ROLE_USER"})  //具有其中一个角色即可
    @GetMapping("/rolesAllowed")
    public String rolesAllowed(){
        return "RolesAllowed";
    }

}


原理:

在这里插入图片描述

  • ConfigAttribute 在Sring Security中,用户请求一个资源(通常是一个接口或者一个Java 方法)需要的角色会被封装成一个ConfigAttribute对象,在ConfigAttribute中只有一个getAttribute方法,该方法返回一个String字符串,就是角色的名称。一般来说,角色名称都带有一个ROLE_ 前缀,投票器AccessDecisionVoter所做的事情,其实就是比较用户所具有的各个角色和请求某个资源所需的ConfigAttribue之间的关系。

  • AccessDecisionVoter和AccessDecisionManager都有众多的实现类,在AccessDecisionManager中会逐个遍历AccessDecisionVoter,进而决定是否允许用户访问,因而AccessDecisionVoter和AccessDecisionManager两者的关系类似于AuthenticationProvider和ProviderManager的关系。

  • 小结

    1. 基于url授权

      访问目标资源之前,进行权限校验.

    2. 基于方法授权(web层controller类里面的方法)

      访问目标方法之前,进行权限校验.

      • jsr250: 是有java提供的授权方式

      • springsecurity自身提供的基于方法的授权

      • springSecurity和shiro两个权限校验框架最大的不同

        1. springSecurity必须在web开发环境里面使用.

        2. shiro可以用在任意项目里面

1.6 springSecurity入门

  • 步骤一: 导入springSecurity依赖以及相关依赖

      <properties>
               <spring.security.version>5.0.1.RELEASE</spring.security.version>
        </properties>
        <!-- web 工程必备,包含过滤器和相关的 Web 安全基础结构代码-->
    	<dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-web</artifactId>
                <version>${spring.security.version}</version>
            </dependency>
    	<!--用于解析 xml 配置文件-->
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-config</artifactId>
                <version>${spring.security.version}</version>
            </dependency>
    		<!--任何 Spring Security 功能都需要此包-->
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-core</artifactId>
                <version>${spring.security.version}</version>
            </dependency>
    		<!--提供的动态标签库,jsp 页面可以用-->
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-taglibs</artifactId>
                <version>${spring.security.version}</version>
            </dependency>
    
  • 步骤二: 配置spring-security.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:security="http://www.springframework.org/schema/security"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/security
            http://www.springframework.org/schema/security/spring-security.xsd">
        <!--
           配置Spring Security
           auto-config="true",如果没有认证,第一次自动生成登录页面,没有认证通过,自动提示错误信息等等
           use-expressions="true"表示使用spring的el表达式来配置spring security
       -->
        <security:http auto-config="true" use-expressions="false">
            <!-- intercept-url定义一个过滤规则 pattern表示对哪些url进行权限控制,access属性表示在请求对应
            的URL时需要什么权限,
            默认配置时它应该是一个以逗号分隔的角色列表,请求的用户只需拥有其中的一个角色就能成功访问对应
            的URL -->
            <security:intercept-url pattern="/**" access="ROLE_USER" />
            <!-- auto-config配置后,不需要在配置下面信息 <security:form-login /> 定义登录表单信息
            <security:http-basic
            /> <security:logout /> -->
        </security:http>
        <!-- (noop: no  operation) 不加密算法-->
        <security:authentication-manager>
            <security:authentication-provider>
                <security:user-service>
                    <security:user name="user" password="{noop}user"
                                   authorities="ROLE_USER" />
                    <security:user name="admin" password="{noop}admin"
                                   authorities="ROLE_ADMIN" />
                </security:user-service>
            </security:authentication-provider>
        </security:authentication-manager>
    </beans>
    
  • 步骤三:配置web.xml

    如果有spring的配置文件,可以将spring-security的配置文件引入到spring的配置文件里面去

    <!--在spring的配置文件里面,引入SpringSecurity主配置文件-->
    <import resource="classpath:spring-security.xml"/>
    

    如果没有spring的配置文件,单独加载

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             version="3.1">
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-security2.xml</param-value>
        </context-param>
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <filter>
            <filter-name>springSecurityFilterChain</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        </filter>
        <filter-mapping>
            <!--固定的springSecurityFilterChain-->
            <filter-name>springSecurityFilterChain</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <welcome-file-list>
            <welcome-file>index.html</welcome-file>
            <welcome-file>index.htm</welcome-file>
            <welcome-file>index.jsp</welcome-file>
            <welcome-file>default.html</welcome-file>
            <welcome-file>default.htm</welcome-file>
            <welcome-file>default.jsp</welcome-file>
        </welcome-file-list>
    </web-app>
    
    

在这里插入图片描述

当我们访问index.html页面时发现会弹出登录窗口,可能你会奇怪,我们没有建立下面的登录页面,为什么Spring
Security会跳到上面的登录页面呢?这是我们设置http的auto-config=”true”时Spring Security自动为我们生成的。

1.7 入门自定义页面

  • 步骤一:导入依赖省略

  • 步骤二: 修改spring-security.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:security="http://www.springframework.org/schema/security"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security.xsd">
        <!-- 配置不过滤的资源(静态资源及登录相关) -->
        <security:http security="none" pattern="/login.html" />
        <security:http security="none" pattern="/failer.html" />
        <security:http auto-config="true" use-expressions="false" >
            <!-- 配置资料连接,表示任意路径都需要ROLE_USER权限 -->
        <security:intercept-url pattern="/**" access="ROLE_USER" />
            <!-- 自定义登陆页面,login-page 自定义登陆页面 authentication-failure-url 用户权限校验失败之后才会跳转到这个页面,如果数据库中没有这个用户则不会跳转到这个页面。
                default-target-url 登陆成功后跳转的页面。 注:登陆页面用户名固定 username,密码 password,action:login -->
            <security:form-login login-page="/login.html"
                                 login-processing-url="/login" username-parameter="username"
                                 password-parameter="password" authentication-failure-url="/failer.html"
                                 default-target-url="/success.html" authentication-success-forward-url="/success.html"
            />
    
            <!-- 关闭CSRF(跨域访问),默认是开启的,这里为true是设置关闭 -->
            <security:csrf disabled="true" />
        </security:http>
        <security:authentication-manager>
            <security:authentication-provider>
                <security:user-service>
                    <security:user name="user" password="{noop}user"
                                   authorities="ROLE_USER" />
                    <security:user name="admin" password="{noop}admin"
                                   authorities="ROLE_ADMIN" />
                </security:user-service>
            </security:authentication-provider>
        </security:authentication-manager>
    </beans>
    

1.8 springSecurity认证使用数据库认证

  • 没有springSecurity的登录流程图:

在这里插入图片描述

没有springSecurity框架, 实现认证的流程

  1. 访问login.jsp登录页面, 在登录页面输入用户名和密码,提交到userControleller
  2. UserController里面: 获取用户信息, 调用注入的Service层的对象去查询用户信息
  3. UserService里面: 调用注入UserMapper,根据用户和密码查询sys_user表

UserController ---->UserService-------------->UserMapper (根据用户名和密码去查询sys_user表,查询出来了,封装UserInfo)

在service层拿到dao层 返回的 UserInfo.

如果userInfo==null,  说明根据用户名和密码没有找到用户, 认证不通过,反之认证通过.

优点:

  1. 认证流程简单清晰

缺点:

 没有实现授权操作,我们手动实现:  
  1. 根据用户名查询出该用户匹配的角色,在根据角色查询所对应的权限资源
  2. 手动授权时机: 方式一: 在过滤器里面授权, 方式二: 基于aop授权.
  • 基于springSecurity的认证和授权流程图

在这里插入图片描述

回顾:

  1. springSecurity是基于角色进行授权(手动封装数据到UserDetails.)

    实现的原理: 将认证通过的信息,以及该用户的角色信息保存到springSecurity提供的: UserDetails.

  2. springSecurity怎么授权的(自动实现)

    底层: UserDetails 根据自身的service, 拿到UserDetails里面的角色数据,查询该角色与之匹配的权限资源.

问题:

  1. 如何将查询出来的UserInfo数据封装到springSecurity提供的 UserDetails

  2. 如何根据用户信息查询与用户匹配的角色数据 ,封装到UserDetails

    UserDetails是一个接口, springSecurity提供了一个实现User ,封装user数据,实现封装 UserDetails

  3. springSecurity提供了一个不可逆的加密算法: BCryptPasswordEncoder,加密后的数据不能再解密了

    问题: 如何实现认证?

    解决方案:

    1. 根据用户名去查询sys_user表, 查询出来加密后的密码

    2. 由springSecurity提供的 UserDetailsService 根据用户提交的密码 与 数据库的加密后的密码进行判断

      如果用户提交的密码 经过匹配, 与加密后的密码一致, 表示认证通过

      如果用户提交的密码 经过匹配, 与加密后的密码不一致, 表示认证不通过

      简单分析如下:

      import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
      //666 = $2a$10$XTSjCY.IMfOi1NbKOyrDLOU11yLX.mTZIaxJ.okSHE8Ypc/Iao/ze
      //666 = $2a$10$zVDtFfT/RP7xDkUQ72cP0.jjAQEGAKansDdZV3RRKB/0kxh0Lf5fW
      
      /**
       * 不可逆的加密算法底层实现
       *  原理: 在对数据进行加密前, 对被加密的数据进行了加salt处理.
       *  什么是salt盐呢?
       *  String[] salts = ["aaa","bbb","ccc","ddd"-----]
       *
       *  实现:
       *  1. 通过一个随机数, 从数组里面获取一个salt
       *  2. 将salt存到某个位置
       *  3. 让(salt+被加密的数据) 通过加密算法进行计算, 得到加密后的数据
       *  springsecurity的认证实现
       *   1.根据用户名查询出来用户信息: 加密后的密码: tablePassword
       *   2.拿到前台前台传递过来的密码: 没有加密的密码
       *   3.springseurity会拿到(salt+没有加密的密码),通过加密算法得到加密后的数据: operPassword
       *     
       *  如果用户提交的密码 经过匹配, 与加密后的密码一致, 表示认证通过
       * 如果用户提交的密码 经过匹配, 与加密后的密码不一致, 表示认证不通过
       */
      public class Demo {
          public static void main(String[] args){
              BCryptPasswordEncoder be = new BCryptPasswordEncoder();
              String p1 = be.encode("666");
              System.out.println(p1);
          }
      }
      
      

**分析:**我们自定义的UserInfo和UserDetails怎么转换呢?

  • org.springframework.security.core.userdetails

    public interface UserDetails extends Serializable {
        Collection<? extends GrantedAuthority> getAuthorities();//指的角色数据
    
        String getPassword();
    
        String getUsername();
    
        boolean isAccountNonExpired();// 帐户是否过期
    
        boolean isAccountNonLocked();//帐户是否锁定
    
        boolean isCredentialsNonExpired();//认证是否过期
    
        boolean isEnabled();//帐户是否可用
    }
    
    
  • 实现类 org.springframework.security.core.userdetails.User

    public class User implements UserDetails, CredentialsContainer {
        private static final long serialVersionUID = 500L;
        private String password;
        private final String username;
        private final Set<GrantedAuthority> authorities;
        private final boolean accountNonExpired;
        private final boolean accountNonLocked;
        private final boolean credentialsNonExpired;
        private final boolean enabled;
    
  • 所以自定义UserInfo转成 UserDetails,代码如下

 @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserInfo userInfo = null;
        try {
            userInfo = userDao.findByUsername(username);
        } catch (Exception e) {
            e.printStackTrace();
        }
        List<SimpleGrantedAuthority> authorities = getAuthority(userInfo.getRoles());
        User user = new User(userInfo.getUsername(),
                "{noop}"+userInfo.getPassword(), //没有密码加密,对应的springsecurity配置文件不能配置加密的bean
                userInfo.getStatus() == 0 ? false : true,
                true,
                true,
                true,
                authorities);
        return user;
    }

    //作用就是返回一个List集合,集合中装入的是角色描述(这里没有根据用户名查询查询用户关联的角色,后期自己查询数据库)
    public List<SimpleGrantedAuthority> getAuthority(List<Role> roles) {

        List<SimpleGrantedAuthority> list = new ArrayList<>();
         list.add(new SimpleGrantedAuthority("ROLE_USER"));
         list.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        return list;
    }
<!-- 切换成数据库中的用户名和密码 -->
    <security:authentication-manager>
    	<security:authentication-provider user-service-ref="userService">
    		<!-- 配置加密的方式 -->
    		<!--<security:password-encoder ref="passwordEncoder"/>-->
    	</security:authentication-provider>
    </security:authentication-manager>
    
    <!-- 配置加密类 -->
    <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
    

  • 基于数据库实现认证操作

    • 步骤一: 配置pom.xm
    • 步骤二: 定义web层, service层, dao层.

1.9 源码分析

问题:为什么DelegatingFilterProxy的filter-name必须是springSecurityFilterChain

在这里插入图片描述

DelegatingFilterProxy并不是真正的Filter,在其initFilterBean方法中会从WebApplicationContext根据delegate
来获取到

protected void initFilterBean() throws ServletException {
        synchronized(this.delegateMonitor) {
            if (this.delegate == null) {
                if (this.targetBeanName == null) {
                    this.targetBeanName = this.getFilterName();
                }

                WebApplicationContext wac = this.findWebApplicationContext();
                if (wac != null) {
                    this.delegate = this.initDelegate(wac);
                }
            }

        }
    }

在上这代码中this.targetBeanName=getFilterName()就是获取名称叫做springSecurityFilterChain
通过在doFilter就去中我们会发现真正干活的其实是delegate这个Filter,而delegate其实就是FilterChainProxy

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized(this.delegateMonitor) {
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
                    WebApplicationContext wac = this.findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
                    }

                    delegateToUse = this.initDelegate(wac);
                }

                this.delegate = delegateToUse;
            }
        }

        this.invokeDelegate(delegateToUse, request, response, filterChain);
    }

FilterChainProxy是spring在解析配置文件时装配到上下文中,并且beanName为springSecurityFilterChain
因此在web.xml中需要配置filter-name为springSecurityFilterChain

spring-security.xml配置文件解析
<!-- 配置不拦截的资源 -->
<security:http pattern="/login.jsp" security="none"/>
<security:http pattern="/failer.jsp" security="none"/>
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/plugins/**" security="none"/>
<security:http auto-config="true" use-expressions="false">
<security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN"/>
<security:form-login
login-page="/login.jsp"
login-processing-url="/login.do"
default-target-url="/index.jsp"
authentication-failure-url="/failer.jsp"
authentication-success-forward-url="/pages/main.jsp"
/>
</security:http>

http标签是自定义标签,我们可以在spring-security-config包中查看

http://www.springframework.org/schema/security=org.springframework.security.config.SecurityName
spaceHandler

继续查看SecurityNamespaceHandler类,在其init方法

public void init() {
loadParsers();
}
//在loadParsers()方法中,指定由HttpSecurityBeanDefinitionParser进行解析
parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
//在HttpSecurityBeanDefinitionParser完成具体解析的parse方法中
registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));
//这里就是注册了名为springSecurityFilterChain的filterChainProxy类
//接下我们在看一下注册一系列Filter的地方createFilterChain,在这个方法中我们重点关注
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element,
forceAutoConfig, pc, httpBldr.getSessionCreationPolicy(),
httpBldr.getRequestCache(), authenticationManager,
httpBldr.getSessionStrategy(), portMapper, portResolver,
httpBldr.getCsrfLogoutHandler());

我们可以查看AuthenticationConfigBuilder创建代码

public AuthenticationConfigBuilder(Element element, boolean forceAutoConfig,
ParserContext pc, SessionCreationPolicy sessionPolicy,
BeanReference requestCache, BeanReference authenticationManager,
BeanReference sessionStrategy, BeanReference portMapper,
BeanReference portResolver, BeanMetadataElement csrfLogoutHandler) {
this.httpElt = element;
this.pc = pc;
this.requestCache = requestCache;
autoConfig = forceAutoConfig
| "true".equals(element.getAttribute(ATT_AUTO_CONFIG));
this.allowSessionCreation = sessionPolicy != SessionCreationPolicy.NEVER
&& sessionPolicy != SessionCreationPolicy.STATELESS;
this.portMapper = portMapper;
this.portResolver = portResolver;
this.csrfLogoutHandler = csrfLogoutHandler;
createAnonymousFilter();
createRememberMeFilter(authenticationManager);
createBasicFilter(authenticationManager);
createFormLoginFilter(sessionStrategy, authenticationManager);
createOpenIDLoginFilter(sessionStrategy, authenticationManager);
createX509Filter(authenticationManager);
createJeeFilter(authenticationManager);
createLogoutFilter();
createLoginPageFilterIfNeeded();
  createUserDetailsServiceFactory();
createExceptionTranslationFilter();
}

分析如下:

在这里插入图片描述

2.0 服务器端方法级权限控制

在服务器端我们可以通过Spring security提供的注解对方法来进行权限控制。Spring Security在方法的权限控制上
支持三种类型的注解,JSR-250注解、@Secured注解和支持表达式的注解,这三种注解默认都是没有启用的,需要
单独通过global-method-security元素的对应属性进行启用

基于方法权限控制,需要开启注解,常用有两种方式:

  • 方式一; 通过配置文件, 开启注解的使用

    1. <security:global-method-security jsr250-annotations=“enabled”/>

      jsr250注解.需要额外引入依赖

    2. <security:global-method-security secured-annotations=“enabled”/>

      springSecurity自身的,不需额外引入依赖

  • 方式二:注解开启
    @EnableGlobalMethodSecurity :Spring Security默认是禁用注解的,要想开启注解,需要在继承
    WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解,并在该类中将
    AuthenticationManager定义为Bean。

2.0.1 JSR-250注解

  • 概述

    在spring-security.xml文件中启用JSR250注解支持。

    JSR概念 翻译自: Wikipedia 维基百科全书

    JSR 250是Java规范请求以发展为目标注解(即,关于不属于程序本身的软件程序的信息)JavaSE和JavaEE适用于各种不同技术的平台。据设想,各种JSR将使用注释来启用陈述式编程风格。在JavaEE组件JSR中保持一致性特别有价值,但允许JavaEE和JavaSE之间的一致性也是很有价值的。

  • 具体实现

    步骤一: 在pom.xml导入依赖

    <!-- https://mvnrepository.com/artifact/javax.annotation/jsr250-api -->
    <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>jsr250-api</artifactId>
        <version>1.0</version>
    </dependency>
    
    

    步骤二: 在spring-security配置文件上, 开启注解支持

    <security:global-method-security jsr250-annotations="enabled"/>  
    

    步骤三: 在测试上面使用注解

    @RolesAllowed表示访问对应方法时所应该具有的角色

    //查询全部产品
        @RequestMapping("/findAll.do")
        @RolesAllowed("admin")//也可以ROLE_ADMIN
        public ModelAndView findAll() throws Exception {
            ModelAndView mv = new ModelAndView();
            List<Product> ps = productService.findAll();
            mv.addObject("productList", ps);
            mv.setViewName("product-list1");
            return mv;
    
        }
    

    示例:
    @RolesAllowed({“USER”, “ADMIN”}) 该方法只要具有"USER", "ADMIN"任意一种权限就可以访问。这里可以省
    略前缀ROLE_,实际的权限可能是ROLE_ADMIN

    @PermitAll表示允许所有的角色进行访问,也就是说不进行权限控制
    @DenyAll是和PermitAll相反的,表示无论什么角色都不能访问

2.0.2 springSecurity自身注解,基于方法进行授权

  • 步骤一: 在springsecurity.xml配置

    <security:global-method-security secured-annotations=“enabled”/

    @Secured注解标注的方法进行权限控制的支持,其值默认为disabled。

  • 步骤二: @Secured注解,在controller里面的方法上面使用

    注意: @Secured注解里面的角色名称必须带ROLE

    2.0.3 其它注解

@PreAuthorize 在方法调用之前,基于表达式的计算结果来限制对方法的访问

示例:
@PreAuthorize(“#userId == authentication.principal.userId or hasAuthority(‘ADMIN’)”)
void changePassword(@P(“userId”) long userId ){ }
这里表示在changePassword方法执行之前,判断方法参数userId的值是否等于principal中保存的当前用户的
userId,或者当前用户是否具有ROLE_ADMIN权限,两种符合其一,就可以访问该方法。

@PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常

	>示例:
	>@PostAuthorize
	>User getUser("returnObject.userId == authentication.principal.userId or
	>hasPermission(returnObject, 'ADMIN')");  
	>
	>

2.1 页面端标签控制权限(了解)

在jsp页面中我们可以使用spring security提供的权限标签来进行权限控制

  • 步骤一: pom.xml导入坐标依赖

    <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>version</version>
    </dependency>
    
  • 步骤二: jsp页面使用

    <%@taglib uri=“http://www.springframework.org/security/tags” prefix=“security”%>

  • 步骤三:使用标签

    在jsp中我们可以使用以下三种标签,其中authentication代表的是当前认证对象,可以获取当前认证对象信息,例
    如用户名。其它两个标签我们可以用于权限控制

authentication :显示登录的用户名

<security:authentication property="" htmlEscape="" scope="" var=""/>
  • property: 只允许指定Authentication所拥有的属性,可以进行属性的级联获取,如“principle.username”,
    不允许直接通过方法进行调用
  • htmlEscape:表示是否需要将html进行转义。默认为true。
  • scope:与var属性一起使用,用于指定存放获取的结果的属性名的作用范围,默认我pageContext。Jsp中拥
    有的作用范围都进行进行指定
  • var: 用于指定一个属性名,这样当获取到了authentication的相关信息后会将其以var指定的属性名进行存
    放,默认是存放在pageConext中

authorize : authorize是用来判断普通权限的,通过判断用户是否具有对应的权限而控制其所包含内容的显示

<security:authorize access="" method="" url="" var=""></security:authorize>
  • access: 需要使用表达式来判断权限,当表达式的返回结果为true时表示拥有对应的权限
  • method:method属性是配合url属性一起使用的,表示用户应当具有指定url指定method访问的权限,
  • method的默认值为GET,可选值为http请求的7种方法
  • url:url表示如果用户拥有访问指定url的权限即表示可以显示authorize标签包含的内容
  • var:用于指定将权限鉴定的结果存放在pageContext的哪个属性中

限控制

  • 步骤一: pom.xml导入坐标依赖

    <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>version</version>
    </dependency>
    
  • 步骤二: jsp页面使用

    <%@taglib uri=“http://www.springframework.org/security/tags” prefix=“security”%>

  • 步骤三:使用标签

    在jsp中我们可以使用以下三种标签,其中authentication代表的是当前认证对象,可以获取当前认证对象信息,例
    如用户名。其它两个标签我们可以用于权限控制

authentication :显示登录的用户名

<security:authentication property="" htmlEscape="" scope="" var=""/>
  • property: 只允许指定Authentication所拥有的属性,可以进行属性的级联获取,如“principle.username”,
    不允许直接通过方法进行调用
  • htmlEscape:表示是否需要将html进行转义。默认为true。
  • scope:与var属性一起使用,用于指定存放获取的结果的属性名的作用范围,默认我pageContext。Jsp中拥
    有的作用范围都进行进行指定
  • var: 用于指定一个属性名,这样当获取到了authentication的相关信息后会将其以var指定的属性名进行存
    放,默认是存放在pageConext中

authorize : authorize是用来判断普通权限的,通过判断用户是否具有对应的权限而控制其所包含内容的显示

<security:authorize access="" method="" url="" var=""></security:authorize>
  • access: 需要使用表达式来判断权限,当表达式的返回结果为true时表示拥有对应的权限
  • method:method属性是配合url属性一起使用的,表示用户应当具有指定url指定method访问的权限,
  • method的默认值为GET,可选值为http请求的7种方法
  • url:url表示如果用户拥有访问指定url的权限即表示可以显示authorize标签包含的内容
  • var:用于指定将权限鉴定的结果存放在pageContext的哪个属性中
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值