Spring Security入门学习——持续更新
1、基本概念
1.1、认证
用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问
1.2、会话
用户认证通过后,为了避免用户每次操作都进行认证,可以将用户信息保存到会话中。会话就是系统为了保持当前用户的登录状态所提供的的机制,常见的有基于session方式,基于token方式
-
基于session的认证认证方式
用户认证成功厚,在服务端生成用户相关的数据保存在session中,发送给客户端的session_id存放到cookie中,这样客户端每次发送请求时都带上session_id,就可以校验服务器端是否存在session数据,以此完成用户的合法校验,当用户退出系统或者session过期销毁时,客户端的session_id也就无效了,需要重新获取
-
基于token方式认证
用户认证成功后,服务端生成一个token发送给客户端,客户端可以放到cookie或者localStorage中,累每次请求时带上token,服务端收到token通过验证后极客确认身份
1.3、授权
授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。
1.4、授权的数据模型
授权可以简单理解为Who对What进行How的操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mwOx4UaM-1622989793757)(734d325441d9729ed2fb6769e69ef400.png)]
1.5、RBAC
1.5.1 基于角色的控制访问(Role-Based Access Control)
- 可扩展性差
1.5.2、基于资源的访问控制(Resource-Based Access Control)
- 可扩展性强
2、SpringSecurity集成Springboot
SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架
2.1、创建maven工程,导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
2.2、相关配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//定义用户信息服务(查询用户信息)
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("123").authorities("p2").build());
return manager;
}
//密码编码器
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/r/**").authenticated() //所有/r/**的请求必须通过认证才能访问
.anyRequest().permitAll() //其他请求可以访问
.and()
.formLogin()//允许表单登录
.successForwardUrl("/login-success");//自定义登录成功的地址
}
}
-
启动项目测试,
-
访问/login路径,跳转到springsecurity内部的登录页面
-
添加两个资源测试路径
@GetMapping("/r/p1") @ResponseBody public String p1(){ return "p1"; } @GetMapping("/r/p2") @ResponseBody public String p2(){ return "p2"; }
-
配置需要访问资源的权限
http.authorizeRequests() .antMatchers("/r/p1").hasAnyAuthority("p1") .antMatchers("/r/p2").hasAnyAuthority("p2")
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build()); manager.createUser(User.withUsername("lisi").password("123").authorities("p2").build());
-
测试访问
用户如果没有资源的访问权限会被拦截,页面显示403
2.3、工作原理
springsecurity所解决的问题就是安全访问控制,Spring Security对Web资源的保护是通过Filter实现的
2.3.1 结构总览
当初始化Spring Security时,会创建一个名为SpringSecurityFilterChain
的Servlet过滤器,类型为FilterChainProxy,该类实现了javax.servlet.Filter接口
Spring Security功能的实现主要是由一系列过滤器链相互配合完成
SecurityContextPersistenceFilter
:这个filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器)UsernamePasswordAuthenticationFilter
:用于处理来自表单提交的验证。该表单必须提供对应的用户名和密码,其中内部还有登录成功和登录失败后进行处理的AuthenticationSuccessHandler
和AuthenticationFailureHandler
,这些都可以用户自定义FilterSecurityInterceptor
:用于保护web资源,使用AccessDecisionManager对当前用户进行授权访问ExceptionTranslationFilter
:能够捕获来自FilterChain所有的一次,并进行处理。但是它只会处理两类异常:AuthenticationException和AccessDeniedException,其他的一次会继续抛出
2.3.2、认证流程
-
UserDetailService:在该类中进行用户的校验,在项目中可以实现该接口,重写方法
@Service("userDetailService") public class UserDetailServiceImpl implements UserDetailsService { /** * 根据账号查询用户信息 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { System.out.println(username); UserDetails userDetails = User.withUsername("zhangsan").password("123").authorities("p1", "p2").build(); return userDetails; } }
-
Bcrypt密码编码器
//密码编码器 @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }
2.3.3、授权流程
2.4、自定义认证
2.4.1、自定义登录页面
-
前端新建一个login.html页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>登录</title> </head> <body> <h1>login</h1> <form th:action="@{/login}" method="post"> 用户名:<input type="text" name="username"/> 密码:<input type="text" name="password"/> <input type="submit" value="提交"> </form> </body> </html>
-
新建路径映射
@GetMapping("/login") public String toLogin(){ return "login"; }
-
在SecurityConfig的configure方法中定义自定义登录页面
.formLogin()//允许表单登录 .loginPage("/toLogin") //配置自定义登录页面 .loginProcessingUrl("/loginForm") .defaultSuccessUrl("/success")
2.5、会话
当用户通过认证后,用户的信息将存放到会话中
-
获取用户身份
//用户登录后,可以通过会话获取用户信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal();
-
会话控制
机制 描述 always 如果没有session存在就创建一个 ifRequired 如果需要就创建一个Session(默认)登录时 never SpringSecurity将不会创建session,但是如果应用中其他地方创建了session,那么SpringSecurity将会使用它 stateless SpringSecurity将绝不会创建Session,也不会使用session http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
2.6、退出
.logout()
.logoutUrl()
.logoutSuccessUrl()
2.7、授权
授权的方式包括web授权和方法授权,web授权是通过url拦截进行授权,方法授权是通过方法拦截进行授权
-
web授权
通过在配置文件中设置哪些路径需要哪些权限,哪些路径直接可以访问等等
-
方法授权
开启注解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {
在方法上使用
@PreAuthorize,@PostAuthorize,@Secured
三类注解
3、分布式系统认证方案
分布性:每个部分都可以单独部署,服务之间通过网络进行通信
伸缩性:每个部分都可以集群部署,并可以针对部分节点进行硬件及软件扩容,具有一定的伸缩能力
共享性:每个部分都可以作为共享资源对外提供服务,多个部分可能有操作共享资源的情况
开放性:每个部分根据需求都可以对外发布共享资源的访问接口,并可以允许第三方系统访问
3.2、分布式认证需求
-
统一认证授权
提供独立的认证服务,统一处理认证授权
-
应用接入认证
提供扩展和开放能力,提供安全的系统对接机制,并可以开放部分API接入第三方使用,均采用统一认证
3.3、分布式认证方案
3.3.1、技术选型
-
基于session的认证方式
session复制:多台应用服务器之间同步session,使得session保持一致,对外透明
session黏贴:当用户访问集群中的某台服务器后,强制指定后续所有请求均 落在次机器上
session集中存储:将session存储到分布式缓存,所有服务器应用统一从分布式缓存中取出session
优点:可以更好的在服务端进行会话控制,并且安全性高
缺点:基于cookie,在移动客户端不能有效使用,并且不能跨域访问
-
基于token的认证
基于token的方式,服务端不用存储认证数据,易维护扩展性强,客户端可以把token存储到任意地方,并且可以实现web和app统一认证机制。
缺点:token中包含信息,一般数据量大,每次请求都要传递,占用带宽。另外,token的签名验签操作也会给cpu带来额外的处理负担
3.3.2、技术方案
使用token方案
- 适合统一认证的机制,客户端、一方应用、三方应用都遵循一直的认证机制
- token认证方式对第三方接入更加合适,当前留下的开放协议有OAuth2.0、JWT等
- 一般情况下,服务端无需存储会话信息,减轻了服务端压力
4、OAuth2.0
OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或者分享他们数据的所有内容
4.2、Spring CloudSecurity OAuth2
OAuth2.0的服务提供方涵盖了两个服务,即授权服务(认证服务)和资源服务
-
授权服务
应包含对接入端以及登入用户的合法性进行校验并颁发token等功能,对令牌的请求端点由spring MVC控制器实现,下面是配置一个认证服务必须要实现的endpoints:
-
AuthorizationEndpoint服务于认证请求。默认URL:
/oauth/authorize
-
TokenEndpoint服务于访问令牌的请求。默认URL:
/oauth/token
资源服务,应该包含对资源的保护功能,对非法请求进行拦截,对请求中token进行解析鉴权等,下面的过滤器用于实现OAuth2.0资源服务:
-
OAuth2AuthenticationProcessingFilter用来对请求给出身份令牌解析鉴权
分别创建UAA授权服务(认证服务)和order订单资源服务
认证流程:
- 客户端请求UAA授权服务进行认证
- 认证通过后由UAA颁发令牌
- 客户端携带令牌Token请求资源服务
-
4.2.1、环境搭建
4.2.1.1、父工程
创建maven工程作为父工程,依赖如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>liuhao.work</groupId>
<artifactId>oauth2-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>oauth2-parent</name>
<description>oauth2-parent</description>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>