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配置文件
server:servlet:context-path: /authport: 53070
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/xc_users?serverTimezone=UTC&userUnicode=true&useSSL=false&username: rootpassword: 123456
第四步: 创建Controller类编写控制器方法,启动工程尝试访问localhost:53070/auth/user/52可以正常访问到数据
package com.xuecheng.auth.controller;
@Slf4j
@RestController
public class LoginController {@AutowiredXcUserMapper 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框架所需的依赖
<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 | 
package com.xuecheng.auth.config;
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic 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;}@Beanpublic 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配置类中创建用户时配置用户的权限
@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
@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的执行流程如下
- 用户提交的用户名、密码被SecurityFilterChain中的UsernamePasswordAuthenticationFilter过滤器获取到并把其封装到Authentication请求对象中
- UsernamePasswordAuthenticationFilter过滤器将- Authentication请求对象提交至认证管理器- AuthenticationManager进行认证
- 认证成功后,AuthenticationManager身份管理器会把权限,身份,细节等信息(密码通常移除)填充到刚提交的Authentication请求对象
- 通过SecurityContextHolder(安全上下文容器)将填充了用户名,权限,身份,细节等信息的Authentication请求对象保存都安全上下文中
AuthenticationManager是认证相关的核心接口也是发起认证的出发点,它的实现类为ProviderManager
- Spring Security支持多种认证方式,所以ProviderManager会维护着一个List<AuthenticationProvider>列表
- 由AuthenticationProvider接口的实现类完成最终实际认证工作(不同实现类代表不同认证方式),web表单的实现类为DaoAuthenticationProvider
- DaoAuthenticationProvider的内部又维护着一个- UserDetailsService负责获取- UserDetails(实体类)
- 最终AuthenticationProvider将获取到的UserDetails信息填充至Authentication请求对象中
