介绍
认证功能与业务无关几乎是每个项目都要具备的功能,市面上有很多认证框架如Apache Shiro、CAS、Spring Security
等
Spring Security
是一个功能强大且可高度定制的身份验证和访问控制
框架,它是一个专注于为Java应用程序提供身份验证和授权
的框架
创建认证服务初始工程
第一步: 在项目根目录下创建一个普通的SpringBoot工程xuecheng-plus-auth
,这个工程可以连接数据库但不具备认证授权功能
第二步: 执行xcplus_users.sql
脚本创建xc_users
数据库
第三步: 在nacos服务端
中新增auth-service-dev.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
可以正常访问到数据
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框架所需的依赖
<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 {
@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
配置类中创建用户时配置用户的权限
@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('权限')")
注解指定用户访问的资源对应所需要的权限,如查询用户信息
时所需要的权限
第三步: 重启工程,如果登录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
进行认证权限,身份,细节
等信息(密码通常移除)填充到刚提交的Authentication
请求对象SecurityContextHolder(安全上下文容器)
将填充了用户名,权限,身份,细节
等信息的Authentication
请求对象保存都安全上下文中AuthenticationManager
是认证相关的核心接口也是发起认证的出发点,它的实现类为ProviderManager
ProviderManager
会维护着一个List<AuthenticationProvider>
列表AuthenticationProvider接口
的实现类完成最终实际认证工作(不同实现类代表不同认证方式),web表单
的实现类为DaoAuthenticationProvider
DaoAuthenticationProvider
的内部又维护着一个UserDetailsService
负责获取UserDetails(实体类)
AuthenticationProvider
将获取到的UserDetails
信息填充至Authentication
请求对象中