01-Spring Security框架的认证和授权测试

Spring Security

介绍

认证功能与业务无关几乎是每个项目都要具备的功能,市面上有很多认证框架如Apache Shiro、CAS、Spring Security

  • Spring Security是Spring家族的一份子且和Spring Cloud集成的很好,所以本项目采用Spring Security作为认证服务的技术框架

Spring Security是一个功能强大且可高度定制的身份验证和访问控制框架,它是一个专注于为Java应用程序提供身份验证和授权的框架

创建认证服务初始工程

第一步: 在项目根目录下创建一个普通的SpringBoot工程xuecheng-plus-auth,这个工程可以连接数据库但不具备认证授权功能

第二步: 执行xcplus_users.sql脚本创建xc_users数据库

第三步: 在nacos服务端中新增auth-service-dev.yaml配置文件

yaml 复制代码
server:
  servlet:
    context-path: /auth
  port: 53070
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/xc_users?serverTimezone=UTC&userUnicode=true&useSSL=false&
    username: root
    password: 123456

第四步: 创建Controller类编写控制器方法,启动工程尝试访问localhost:53070/auth/user/52可以正常访问到数据

java 复制代码
package com.xuecheng.auth.controller;
@Slf4j
@RestController
public class LoginController {
    @Autowired
    XcUserMapper userMapper;

    @RequestMapping("/login-success")
    public String loginSuccess() {
        return "登录成功";
    }

    @RequestMapping("/user/{id}")
    public XcUser getuser(@PathVariable("id") String id) {
        XcUser xcUser = userMapper.selectById(id);
        return xcUser;
    }

    @RequestMapping("/r/r1")
    public String r1() {
        return "访问r1资源";
    }

    @RequestMapping("/r/r2")
    public String r2() {
        return "访问r2资源";
    }
}

认证测试

第一步: 在xuecheng-plus-auth服务工程中添加Spring Security框架所需的依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

第二步: 重启工程访问localhost:53070/auth/r/r1会自动进入/login页面,这个页面是由Spring Security提供的

第三步: 创建config/WebSecurityConfig配置类继承WebSecurityConfigurerAdapter

  • 该类可以用来配置用户信息(账号和密码以及访问权限),密码存储方式(明文还是加密),安全拦截机制(需要对哪些请求路径进行认证)

第四步: 重启工程访问localhost:53070/auth/user/52可以正常访问, 访问localhost:53070/auth/r/r1会被拦截,跳转到Spring Security提供的登录页面

方法 描述
authorizeRequests() 配置请求授权规则
antMatchers() 指定需要进行访问控制的URL路径的匹配规则
authenticated() 指定需要进行身份验证的请求
anyRequest() 表示除了需要进行访问控制的URL以外请求
permitAll() 表示任何用户都可以访问不需要进行身份验证
formLogin() 配置登录页表单认证
successForwardUrl() 指定登录成功后的跳转页面
logout() 配置退出登录页面
logoutUrl() 指定退出登录的URL
java 复制代码
package com.xuecheng.auth.config;
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public UserDetailsService userDetailsService() {
        // 1. 配置用户信息服务
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 2. 创建用户信息这里暂时写死,后面需要从数据库中动态查询, Kyle的权限是p1,Lucy的权限是p2
        // User是Spring Security提供的工具类
        manager.createUser(User.withUsername("Kyle").password("123").authorities("p1").build());
        manager.createUser(User.withUsername("Lucy").password("456").authorities("p2").build());
        return manager;
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 采用明文的方式
        return NoOpPasswordEncoder.getInstance();
    }

    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/r/**")// 表示"/r/**"开头的请求需要认证
            .authenticated()
            .anyRequest().permitAll()// 其他请求全部放行
            .and()
            .formLogin()
            .successForwardUrl("/login-success");
        // 配置退出登录页面,认证成功后访问/logout可退出登录
        http.logout().logoutUrl("/logout");
    }
}

授权测试

用户认证通过后需要去访问系统的资源,但不同的用户对资源的访问权限是不同的

  • Spring Security会判断当前用户是否有该资源的访问权限,如果有权限才可以继续访问没有权限则拒绝访问

第一步: 在WebSecurityConfig配置类中创建用户时配置用户的权限

java 复制代码
@Bean
public UserDetailsService userDetailsService() {
    // 1. 配置用户信息服务, InMemoryUserDetailsManager是指在内存中配置用户的信息
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    // 2. 创建用户信息这里暂时写死,后面需要从数据库中动态查询, Kyle的权限是p1,Lucy的权限是p2
    // User是Spring Security提供的工具类
    manager.createUser(User.withUsername("Kyle").password("123").authorities("p1").build());
    manager.createUser(User.withUsername("Lucy").password("456").authorities("p2").build());
    return manager;
}

第二步:在控制器方法上使用@PreAuthorize("hasAnyAuthority('权限')")注解指定用户访问的资源对应所需要的权限,如查询用户信息时所需要的权限

  • 由于要访问资源需要通过URL,所有我们在Controller中定义的每个HTTP的接口就是访问资源的接口,权限控制就是在接口这里配置

第三步: 重启工程,如果登录Kyle账号进行认证,由于其只有p1权限所以无法访问/r/r2会报403错误, 同理Lucy无法访问/r/r1

java 复制代码
@RequestMapping("/r/r1")
@PreAuthorize("hasAnyAuthority('p1')")// 访问/r/r1需要p1权限
public String r1() {
    return "访问r1资源";
}

@RequestMapping("/r/r2")
@PreAuthorize("hasAuthority('p2')")// 访问/r/r2需要p2权限
public String r2() {
    return "访问r2资源";
}

认证的工作原理

过滤链

Spring Security所解决的问题就是对安全访问控制即对所有进入系统的请求进行拦截, 校验每个请求是否能够访问到它所期望的资源

通过Filter或AOP等技术可以实现安全访问控制功能,而Spring Security对Web资源的保护是靠Filter实现的,Spring Security有一个过滤链

Spring Security框架中真正起作用的是FilterChainProxy中的SecurityFilterChain所包含的各个Filter

过滤器 功能
SecurityContextPresistenceFilter 整个拦截过程的入口和出口即第一个和最后一个拦截器 在请求开始时从配置好的SecurityContextRepository中获取SecurityContext给SecurityContextHolder 在请求完成后将SecurityContextRepository持有的SecurityContext再保存到配置好的SecurityContextRepository,同时清除SecurityContextHolder所持有的SecurityContext
UsernamePasswordAuthenticationFilter 处理来自表单提交的认证,要求表单必须提供对应的用户名和密码 其内部还有登录成功或失败后进行处理的AuthenticationSuccessHandler和AuthenticationFailureHandler
FilterSecurityInterceptor 用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问
ExceptionTranslationFilter 捕获来自 FilterChain所有的异常并进行处理 它只会处理AuthenticationException和AccessDeniedException这两类异常,其它的异常它会继续抛出

执行流程

Spring Security的执行流程如下

  1. 用户提交的用户名、密码被SecurityFilterChain中的UsernamePasswordAuthenticationFilter过滤器获取到并把其封装到Authentication请求对象中
  2. UsernamePasswordAuthenticationFilter过滤器将Authentication请求对象提交至认证管理器AuthenticationManager进行认证
  3. 认证成功后,AuthenticationManager身份管理器会把权限,身份,细节等信息(密码通常移除)填充到刚提交的Authentication请求对象
  4. 通过SecurityContextHolder(安全上下文容器)将填充了用户名,权限,身份,细节等信息的Authentication请求对象保存都安全上下文中

AuthenticationManager是认证相关的核心接口也是发起认证的出发点,它的实现类为ProviderManager

  • Spring Security支持多种认证方式,所以ProviderManager会维护着一个List<AuthenticationProvider>列表
  • AuthenticationProvider接口的实现类完成最终实际认证工作(不同实现类代表不同认证方式),web表单的实现类为DaoAuthenticationProvider
  • DaoAuthenticationProvider的内部又维护着一个UserDetailsService负责获取UserDetails(实体类)
  • 最终AuthenticationProvider将获取到的UserDetails信息填充至Authentication请求对象中
相关推荐
一只叫煤球的猫4 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9654 小时前
tcp/ip 中的多路复用
后端
bobz9655 小时前
tls ingress 简单记录
后端
皮皮林5516 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友6 小时前
什么是OpenSSL
后端·安全·程序员
bobz9656 小时前
mcp 直接操作浏览器
后端
前端小张同学8 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook8 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康9 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在9 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net