# springsecurity学习
**Repository Path**: jkh95/spring-security-learning
## Basic Information
- **Project Name**: springsecurity学习
- **Description**: springsecurity学习
尚硅谷
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-08-31
- **Last Updated**: 2024-08-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 0.为什么 OAuth 里除了 Access Token 之外,还需要 Refresh Token?
直接上连接https://www.jianshu.com/p/bf017aa3fb84

## 1.SpringSecurity集成多种登录方式
https://blog.csdn.net/hou_ge/article/details/120291287?spm=1001.2014.3001.5502
## 2.Oauth2之/oauth/authorize(AuthorizationEndpoint)
主要负责下面几个活:
A.如果没登录,则跳转到授权服务器的登录页面
B.登陆后,跳转到/oauth/confirm_access,跳转到用户【授权】与【取消】的页面(此处可以自定义,可以“信任”)
C.点击授权的话,则调用POST /oauth/authorize,然后重定向回用户的页面+code
文章:https://blog.csdn.net/hou_ge/article/details/122322056?spm=1001.2014.3001.5502
## 3.SpringSession+Redis
在DXP里面用到了SpringSession,场景是这样的:现在用dxp与flow两个服务,且他们都是2个实例,那么现在就是有4台服务器,且他们走的都是同一个网关:http://xxx.xxx.xx.xx:8077/ 那么他们会用一套Cookie,此时比如访问dxp,进行登录了,Session里也有了。此时请求又转发到dxp的另外一个实例上,因为同一个IP,请求也带着 第一次登录后的登录Cookie过来,但是你dxp的另个实例没存Session,咋办,GG。。。。但是你用了SpringSession的话,他会在第一个登录的时候,同步Session到所有的服务器,那么第二次的请求到dxp的另个实例的时候 拿着Cookie,就能匹配上Session了,flow也是同理的。
但是SpringSession有缺点,当并发量大,或者用户登录很多的时候,Session就要耗费大量的服务器资源,更别说要同步给其他服务器。在同步的时差上也会出问题。优化:服务器不存session了,存到redis里,大家都去redis里取。所有SpringSession+Redis的方案,可行。但是不是很好的一个方案。
## 4.WebSecurityConfigurerAdapter顺序问题
起因:DXP系统里有很多WebSecurityConfigurerAdapter,我就像这么多玩意,他们如果控制了同一个url,看谁起效果?比如/org/add 有2个WebSecurityConfigurerAdapter都匹配到了,一个不许认证就可以访问,一个需求,那听谁的?
先说结论,谁先听谁的
分两种情况,
在同一个WebSecurityConfigurerAdapter中,谁先听谁的

如图,此时访问con1,是不需要登录的

如果反过来的话,会跳转到login页面
*********************************************************
在多个WebSecurityConfigurerAdapter中,看Order
 
可以看到,第一个类Order是99,会在第二个类前被加载,所以,优先级更高,所以如果我们访问/con2,是需要登陆后访问的。
## 5.requestMatchers和authorizeRequests的区别?
起因:在配置WebSecurityConfigurerAdapter的configure的时候,需要对匹配的URL进行认证,但是看到requestMatchers、authorizeRequests都能够antMatchers("/xxx/**")进行授权认证,所以有啥区别吗?
理解这两个方法的区别首先要知道springsecurity中,每声明一个WebSecurityConfigurerAdapter实例,就会产生一条过滤器链,一个请求过来要走哪个过滤器链就是由requestMatchers()方法配置的url决定的。请求匹配上requestMatchers()配置的过滤器链后,在进一步的详细控制则是authorizeRequests()决定的。
所以咱们的眼光如果只放在一个WebSecurityConfigurerAdapter上看,看不出来任何问题,但是如果一个项目里有N个WebSecurityConfigurerAdapter,就有区别了。
两个WebSecurityConfigurerAdapter对同一个url都匹配上了 就要看Order来决定他们的优先级了。
多提一嘴:requestMatchers().anyRequest() 等价于 http.authorizeRequests().anyRequest().permitAll();


访问/con1 是不需要登录的,虽然第二个图优先级高,但是他的requestMatchers没有去管理/con1,而第一个图是管理con1,所以要看第一个图对于con1 是怎么管理的
## 6.Oauth2几个模式的区别
发现一篇讲的很好,言简意赅的文章:https://blog.csdn.net/Cr1556648487/article/details/127318889
## 7.(重点)SpringSecurity执行原理
疑问?DXP里有那么多的WebSecurityConfigurerAdapter,他们是怎么工作的,不会相互干扰嘛?比如我写一个过滤器,到底拦截谁呢?
### 7.1多个WebSecurityConfigurerAdapter
实际上一个WebSecurityConfigurerAdapter的实例就可以配置一条过滤器链。如图,我这个项目配置了2个Adapter。(我随意访问一个接口,会被FilterChainProxy拦截)
从图中可以看到,我有两个过滤器链,并且他们的管控范围就是每个adapter的requestMatchers()进行配置的。这个在第5章节提到了。这里再次验证了,每个adapter只会管控自己指定的范围,外面的东西,他不管。
流程:请求进来,被FileChainProxy拦截,调用getFilters进行匹配,看他被哪个adapter管,然后得到这个adapter所有的filter集合,然后调用所有的filter。

注意看这个this就很精髓,因为我们都知道filter执行完 必须放行,放行之后还是调用自己,形成一个循环调用,不过在上面有一个Position指数器,来实现把list一个一个执行完。
这也解释了我们请求的url,只会被管控自己的adapter配置的filter所来拦截,比如我adapter自己配了2个filter,我这个url被他管控,我来请求的时候,会被这2个filter拦截。但是如果我不是这个adapter所管控,这2个filter不会拦截我。
## 8.授权码模式省去授权确认步骤
实现的效果:调用 /oauth/authorize直接直接获取code,不需要再让用户点击授权,才能拿到Code。
授权码模式想要拿授权码Code,是访问接口GET /oauth/authorize 关注下面这个代码即可:
```java
//检验用户是否授权,默认使用的是DefaultUserApprovalHandler对象,实现逻辑等同于是直接判断authorizationRequest对象的approved属性
authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
(Authentication) principal);
boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
authorizationRequest.setApproved(approved);
// 如果已经经过用户授权,如果responseTypes包含token这调用getImplicitGrantResponse()方法,进行跳转,
// 如果responseTypes包含code,则调用getAuthorizationCodeResponse()方法,进行跳转。
if (authorizationRequest.isApproved()) {
if (responseTypes.contains("token")) {
return getImplicitGrantResponse(authorizationRequest);
}
if (responseTypes.contains("code")) {
return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
(Authentication) principal));
}
}
// 如果没有经过用户授权,就会保存authorizationRequest对象,然后调用getUserApprovalPageResponse()方法,跳转到"forward:/oauth/confirm_access";即,登录授权确认页面,该地址在WhitelabelApprovalEndpoint类中定义,并提供了授权确认的接口"/oauth/confirm_access"。
model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, unmodifiableMap(authorizationRequest));
```
其关键就在于`userApprovalHandler.isApproved`。他是读取oauth_client表里的approve字段(每个系统都不一样,但是都会有这么个字段的)。
userApprovalHandler也是可以个性化的。
```java
@Bean
public UserApprovalHandler userApprovalHandler() {
OauthUserApprovalHandler userApprovalHandler = new OauthUserApprovalHandler();
userApprovalHandler.setOauthService(oauthService);
userApprovalHandler.setTokenStore(tokenStore);
userApprovalHandler.setClientDetailsService(this.clientDetailsService);
userApprovalHandler.setRequestFactory(oAuth2RequestFactory());
return userApprovalHandler;
}
```
```java
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenServices(tokenServices)
.tokenStore(tokenStore)
.authorizationCodeServices(authorizationCodeServices)
.userDetailsService(userDetailsService)
.userApprovalHandler(userApprovalHandler())
.authenticationManager(authenticationManager);
}
```
oauth_client每个字段具体什么含义,可以去看开源项目spring-oauth-server里面的db_table_description.html。
## 9.SpringSession + CAS实现统一登出
背景:有个项目,希望实现授权服务器的退出后,当前浏览器的所有子系统统一登出。我们之前一直只关注登入,确实没实现过登出。我看了下Ngportal项目,他们因为使用了SpringSecurity,所以可以基于`session策略`完成登出。但是这个项目很老,没有用到SpringSecurity。此时我突然想打`SSO团队`他们当时的文档里,有提到`CAS登出过滤器`。我一搜,果然CAS开源项目有一个方案,基于java 过滤器的实现。可参考[文章](https://www.cnblogs.com/xiangkejin/p/8963089.html)
但是CAS是内部维护了一个Map进行管理的,而项目上,大概率是集群部署的。那么CAS的套路就不行了,故 我们可以自己照着CAS的思路去实现一版。
下面是我的思路:
- 重写`SessionMappingStorage`,因为CAS基于它做session的维护的,而他不支持集群,故要重写,写一个redis版本的`RedisBackedSessionMappingStorage`,代码的话可以参考[RedisBackedSessionMappingStorage](https://ningyu1.github.io/site/post/54-cas-server/)。
- 重写`SingleSignOutFilter`,因为它比较蛋疼,它是内部new出来的,所以自己重写一下,并且set自己的`RedisBackedSessionMappingStorage`。
下面是幻想时刻:
那么现在按理说,通过CAS的SingleSignOutFilter可以找到需要注销的HttpSession,并且执行它的`session.invalidate();`即可注销Session,也同步到SpringSession里,这样整个集群都会退出。
它会执行`org.springframework.session.web.http.SessionRepositoryFilter.SessionRepositoryRequestWrapper.HttpSessionWrapper#invalidate`,清除redis缓存。