Spring Boot整合Spring Security实现权限控制

学习Spring Security过后进行总结和梳理,首先说一下关于认证授权的基本概念。

基本概念

什么是认证?

​ 进入移动互联网时代,大家每天都在刷手机,常用的软件有微信、支付宝、头条等,下边拿微信来举例子说明认证 相关的基本概念,在初次使用微信前需要注册成为微信用户,然后输入账号和密码即可登录微信,输入账号和密码 登录微信的过程就是认证。

系统为什么要认证

​ 认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。

认证:用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信 息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登录,手 机短信登录*,*指纹认证等方式。

什么是会话?

​ 用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。

基于session的认证方式如下:
它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。

基于token方式如下:

​ 它的交互流程是,用户认证成功后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。

基于session的认证方式由Servlet规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持cookie。

基于token的方式则一般不需要服务端存储token,并且不限制客户端的存储方式。如今移动互联网时代,更多类型的客户端需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于token的方式更适合。

什么是授权?

​ 比如:微信登录成功后用户即可使用微信的功能,如:发红包、发朋友圈、添加好友等,如未绑定银行卡用户则无法发送红包,绑定银行卡的用户才可以发红包,发红包功能、发朋友圈功能都是微信的资源即功能资源,用户拥有发红包功能的权限才可以正常使用发送红包功能,这个根据用户的权限来控制用户使用资源的过程就是授权。

为什么要授权?

​ 认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是在认证通过后发生的,控制不同的用户能够访问不同的资源。

授权: 授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。


Spring Security介绍

​ Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。由于它是Spring生态系统中的一员,因此它伴随着整个Spring生态系统不断修正、升级,在spring boot项目中加入spring security更是十分简单,使用Spring Security 减少了为企业系统安全控制编写大量重复代码的工作。

特征
  • 对身份验证和授权的全面和可扩展的支持
  • 防止会话固定,点击劫持,跨站点请求伪造等攻击
  • Servlet API集成
  • 可选与Spring Web MVC集成

下面我们来实现一个简单认证授权的demo,首先引入pom.xml

 <!-- 以下是>spring boot依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!-- 以下是>spring security依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
            <version>3.0.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- Spring Boot热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>javax.persistence-api</artifactId>
            <version>2.2</version>
        </dependency>
编写SpringSecurity的配置类,该类需要继承WebSecurityConfigurerAdapter

​ spring security提供的用户名密码登录、退出、会话管理等认证功能,只需要配置即可使用,安全配置的内容包括:用户信息、密码编码器、安全拦截机制。

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/r/r1").hasAuthority("p2")
                .antMatchers("/r/r2").hasAuthority("p3")
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
                .and()
                //开启自动配置的登陆功能,如果没有登录,则会来到登录页面
                .formLogin()//允许表单登录
                //.loginPage("/login-view")//自定义登录页面
                .loginProcessingUrl("/login")
                .successForwardUrl("/login-success")
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)//自定义登录成功的页面地址
                .and()
                .logout()
                .logoutUrl("/logout")//访问/logout表示用户注销,清空session
                .logoutSuccessUrl("/");//注销以后来到首页

    }
}

自定义UserDetailsService
@Service
public class SpringDataUserDetailsService implements UserDetailsService {

    @Autowired
    private UserDao userDao;

    //根据 账号查询用户信息
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //将来连接数据库根据账号查询用户信息
        TUser user = userDao.getUserByUsername(username);
        if (user == null) {
            //如果用户查不到,返回null,由provider来抛出异常
            return null;
        }
//      根据用户的id查询用户的权限
        List<String> permissions = userDao.findPermissionsByUserId(user.getId());
//        //将permissions转成数组
        String[] permissionArray = new String[permissions.size()];
        permissions.toArray(permissionArray);
        UserDetails userDetails = User.withUsername(user.getUsername()).password(user.getPassword()).authorities(permissionArray).build();
        return userDetails;
    }
}

​ UserDetailsService 接口只有一个方法,loadUserByUsername(String username),一般需要我们实现此接口方法,根据用户名加载登录认证和访问授权所需要的信息,并返回一个 UserDetails的实现类,后面登录认证和访问授权都需要用到此中的信息。

​ UserDetails 提供了一个默认实现 User,主要包含用户名(username)、密码(password)、权限(authorities)和一些账号或密码状态的标识。如果默认实现满足不了你的需求,可以根据需求定制自己的 UserDetails,然后在 UserDetailsService 的 loadUserByUsername 中返回即可。


LoginController内容
@RestController
public class LoginController {

    @RequestMapping(value = "/login-success",produces = {"text/plain;charset=UTF-8"})
    public String loginSuccess(){
        //提示具体用户名称登录成功
        return getUsername()+" 登录成功";
    }

    /**
     * 测试资源1
     * @return
     */
    @GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF-8"})
//  要使用注解限制方法访问权限需要开启WebSecurityConfig中@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
//    @PreAuthorize("hasAuthority('p1')")//拥有p1权限才可以访问,方法调用的权限控制。
    public String r1(){
        return getUsername()+" 访问资源1";
    }

    /**
     * 测试资源2
     * @return
     */
    @GetMapping(value = "/r/r2",produces = {"text/plain;charset=UTF-8"})
//    @PreAuthorize("hasAnyAuthority('p1','p3')")//拥有p2权限才可以访问
//    @PreAuthorize("hasAuthority('p2')")//拥有p1权限才可以访问
    public String r2(){
        return getUsername()+" 访问资源2";
    }

    //获取当前用户信息
    private String getUsername(){
        String username = null;
        //当前认证通过的用户身份
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        //用户身份
        Object principal = authentication.getPrincipal();
        if(principal == null){
            username = "匿名";
        }
        if(principal instanceof UserDetails){
            UserDetails userDetails = (UserDetails) principal;
            username = userDetails.getUsername();
        }else{
            username = principal.toString();
        }
        return username;
    }
}

工作原理

1、结构总览

​ Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过Filter或AOP等技术来实现,SpringSecurity对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。

​ 当初始化Spring Security时,会创建一个名为 SpringSecurityFilterChain 的Servlet过滤器,类型为
org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,下图是Spring Security过虑器链结构图:

在这里插入图片描述

FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器
(AccessDecisionManager)进行处理,下图是FilterChainProxy相关类的UML图示。
在这里插入图片描述

spring Security功能的实现主要是由一系列过滤器链相互配合完成。
在这里插入图片描述

下面介绍过滤器链中主要的几个过滤器及其作用:

  1. SecurityContextPersistenceFilter这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截
    器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给
    SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext;
  2. UsernamePasswordAuthenticationFilter 用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和
  3. AuthenticationFailureHandler,这些都可以根据需求做相关改变;
  4. FilterSecurityInterceptor 是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问,前面已经详细介绍过了;
  5. ExceptionTranslationFilter 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常:AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。
2、认证流程

在这里插入图片描述

让我们仔细分析认证过程:

  1. 用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到,
    封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
  2. 然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证
  3. 认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除) Authentication 实例。
  4. SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。
    可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个
    List 列表,存放多种认证方式,最终实际的认证工作是由
    AuthenticationProvider 完成的。咱们知道web表单的对应的AuthenticationProvider 实现类为 DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService 负责UserDetails的获取。最终AuthenticationProvider将UserDetails填充至Authentication。

我们来看一下Authentication(认证信息)的结构,它是一个接口

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    Object getCredentials();

    Object getDetails();

    Object getPrincipal();

    boolean isAuthenticated();

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
  1. Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于 java.security
    包中的。它是表示着一个抽象主体身份,任何主体都有一个名称,因此包含一个getName()方法。
  2. getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
  3. getCredentials(),凭证信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
  4. getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
  5. getPrincipal(),身份信息,大部分情况下返回的是UserDetails接口的实现类,UserDetails代表用户的详细信息,那从Authentication中取出来的UserDetails就是当前登录用户信息,它也是框架中的常用接口之一。
3、授权流程

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

Spring Security的授权流程如下:

在这里插入图片描述

分析授权流程:
  1. 拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中的 FilterSecurityInterceptor 的子
    类拦截。

  2. 获取资源访问策略,FilterSecurityInterceptor会从 SecurityMetadataSource 的子类
    DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限
    Collection 。
    SecurityMetadataSource其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访问规则, 读取访问策略如:

    http
    .authorizeRequests()
    .antMatchers("/r/r1").hasAuthority("p1")
    .antMatchers("/r/r2").hasAuthority("p2")
    
  3. 最后,FilterSecurityInterceptor会调用 AccessDecisionManager 进行授权决策,若决策通过,则允许访问资源,否则将禁止访问

最终效果:

1、测试认证

在这里插入图片描述

2、测试授权

登录成功之后访问需要授权页面,未拥有权限时返回状态403

在这里插入图片描述

有该权限时则直接访问资源

在这里插入图片描述

到此使用Spring Boot整合Spring Security实现简单的权限控制完成,写这个demo只是为了熟悉流程,还有更多的知识等着我们去学习。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值