SpringSecurityOAuth核心源码解析

蓝色表示接口,绿色表示类

1,TokenEndpoint 整个入口点,相当于一个controller,不同的授权模式获取token的地址都是 /oauth/token ,通过grant_type 参数标识不同的授权类型,这个类就是判断授权类型 grant_type的。

2,TokenEndpoint收到请求后先调用 ClientDetailsService 读取第三方应用的信息。client_id、client_secret 来获取不同的第三方应用信息,封装在ClientDetails里;TokenEndpoint还会创建一个TokenRequest 的对象 ,封装了请求grant_type等信息,将ClientDetails放在TokenRequest 里

3,TokenRequest  会调用TokenGranter (令牌授权者)四种授权模式的实现,根据请求的grant_type 挑一种令牌生成的逻辑,都会产生OAuth2Request(整合了ClientDetails+TokenRequest) 、 Authentication(封装授权用户的信息,通过UserDetailsService获取),这两个对象整合起来生成 OAuth2Authentication 对象(包装了哪个第三方应用在请求哪个用户授权,授权模式,授权参数等信息),

4,OAuth2Authentication 最后传给AuthorizationServerTokenServices(认证服务器令牌服务)的默认实现,生成一个令牌。AuthorizationServerTokenServices 里还有TokenStore用来处理令牌的存取,TokenEnhancer 令牌增强器。

重构三种登录方式

要做的就是在登录成功后,按照Spring Security OAuth 生成 access_token 的逻辑,生成自己的token。

分析:

逆着推,要想有OAuth2AccessToken ------>AuthorizationServerTokenServices(spring已提供默认实现)------>OAuth2Authentication对象--------->Authentication(登录成功后会有这个对象)、OAuth2Request。

所以只需要构建OAuth2Request即可。

OAuth2Request-------->ClientDetails( 封装第三方应用的信息)------------>ClientDetailsService------------>接收ClientId 构建ClientDetails-------------->请求参数

       --------->TokenRequest-------->根据ClientDetails信息new出来

此部分代码都是app项目里的

其中一部分是从BasicAuthenticationFilter 类里copy一段代码,这个类是处理Basic登录用的,copy一段获取请求头信息的

ImoocAuthenticationSuccessHandler:

/**
* 认证成功后做的处理
* ClassName: ImoocAuthenticationSuccessHandler
* @Description: 认证成功后做的处理
* @author lihaoyang
* @date 2018年3月1日
*/
@Component("imoocAuthenticationSuccessHandler")
public class ImoocAuthenticationSuccessHandler
//spring默认的登录成功处理器,实现了AuthenticationSuccessHandler接口,适配登录后 重定向和返回json两种用这个实现
extends SavedRequestAwareAuthenticationSuccessHandler
//返回json用这个实现
/*implements AuthenticationSuccessHandler*/{ private Logger logger = LoggerFactory.getLogger(getClass()); //springmvc启动会自动注册一个ObjectMapper
@Autowired
private ObjectMapper objectMapper; @Autowired
private SecurityProperties securityProperties; @Autowired
private ClientDetailsService clientDetailsService; @Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices; @Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException { logger.info("登录成功");
//获取请求头里Authorization信息
String header = request.getHeader("Authorization");
//没有client信息
if (header == null || !header.startsWith("Basic ")) {
throw new UnapprovedClientAuthenticationException("请求头中无client信息");
} /**
* 构造OAuth2Request 第一步,从请求头获取clientId
*/
//base64解码获取clientId、clientSecret
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2; String clientId = tokens[0];
String clientSecret = tokens[1];
/**
* 构造OAuth2Request 第二步,根据clientId 获取ClientDetails对象
*/
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); if(clientDetails == null){
throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在,clientId:"+clientId);
}else if(!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)){
throw new UnapprovedClientAuthenticationException("clientSecret不匹配,clientId:"+clientId);
} /**
* 构造OAuth2Request 第三步,new TokenRequest
* 第一个参数是个map,放的是每个授权模式特有的参数,springsecurity会根据这些参数构建Authentication
* 我们这里已获取到了Authentication,弄个空的map就可
*/
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom"); /**
* 构造OAuth2Request 第四步,创建 OAuth2Request
*/
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
/**
* 构造OAuth2Request 第五步,创建 OAuth2Authentication
*/
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
/**
* 构造OAuth2Request 第六步,authorizationServerTokenServices创建 OAuth2AccessToken
*/
OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication); response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(accessToken)); } /**
* base64解码请求头 Basic aW1vb2M6aW1vb2NzZWNyZXQ=
* Decodes the header into a username and password.
*
* @throws BadCredentialsException if the Basic header is not present or is not valid
* Base64
*/
private String[] extractAndDecodeHeader(String header, HttpServletRequest request)
throws IOException {
//Basic aW1vb2M6aW1vb2NzZWNyZXQ= 截取Basic后的
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
//解码后格式 用户名:密码
decoded = Base64.decode(base64Token);
}
catch (IllegalArgumentException e) {
throw new BadCredentialsException(
"Failed to decode basic authentication token");
} String token = new String(decoded, "UTF-8"); int delim = token.indexOf(":"); if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
}
//返回的数组是 [用户名(就是client_id),clientSecret] 其实就是配置的
/**
* security.oauth2.client.clientId = imooc
security.oauth2.client.clientSecret = imoocsecret
*/
return new String[] { token.substring(0, delim), token.substring(delim + 1) };
} }

ImoocResourceServerConfig:安全配置

/**
* 资源服务器,和认证服务器在物理上可以在一起也可以分开
* ClassName: ImoocResourceServerConfig
* @Description: TODO
* @author lihaoyang
* @date 2018年3月13日
*/
@Configuration
@EnableResourceServer
public class ImoocResourceServerConfig extends ResourceServerConfigurerAdapter{ //自定义的登录成功后的处理器
@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler; //自定义的认证失败后的处理器
@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailureHandler; //读取用户配置的登录页配置
@Autowired
private SecurityProperties securityProperties; @Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig; @Override
public void configure(HttpSecurity http) throws Exception { http //----------表单认证相关配置---------------
.formLogin()
.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL) //处理用户认证BrowserSecurityController
.loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
.successHandler(imoocAuthenticationSuccessHandler)//自定义的认证后处理器
.failureHandler(imoocAuthenticationFailureHandler) //登录失败后的处理
.and()
//-----------授权相关的配置 ---------------------
.authorizeRequests()
// /authentication/require:处理登录,securityProperties.getBrowser().getLoginPage():用户配置的登录页
.antMatchers(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
securityProperties.getBrowser().getLoginPage(),//放过登录页不过滤,否则报错
SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
SecurityConstants.SESSION_INVALID_PAGE,
SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*").permitAll() //验证码
.anyRequest() //任何请求
.authenticated() //都需要身份认证
.and()
.csrf().disable() //关闭csrf防护
.apply(smsCodeAuthenticationSecurityConfig);//把短信验证码配置应用上 } }

启动demo项目,demo项目此时依赖app项目,此时做的是表单登录的处理

访问表单登录:添加Authorization信息,username配置的cilentId:imooc  ,password配置的clientSecret:imoocsecret

#第三方应用client_id
security.oauth2.client.clientId = imooc
#注意clientSecret 大写
security.oauth2.client.clientSecret = imoocsecret

响应token:

{
"access_token": "f75ee2e8-a20b-4790-a3b9-64198766dc64",
"token_type": "bearer",
"refresh_token": "013f25fa-affc-445e-bdf6-2adb021aa60d",
"expires_in": 43199
}

拿着token 访问用户信息请求 http://127.0.0.1:8080/user/me,header加上

响应:

{
"password": null,
"username": "user",
"authorities":[
{
"authority": "ROLE_USER"
},
{
"authority": "admin"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true,
"userId": "user"
}

至此可以通过通过用户名密码获取token,拿着token访问用户信息了!

具体代码放在了github https://github.com/lhy1234/spring-security

Spring Security构建Rest服务-1202-Spring Security OAuth开发APP认证框架之重构3种登录方式的更多相关文章

  1. Spring Security构建Rest服务-1300-Spring Security OAuth开发APP认证框架之JWT实现单点登录

    基于JWT实现SSO 在淘宝( https://www.taobao.com )上点击登录,已经跳到了 https://login.taobao.com,这是又一个服务器.只要在淘宝登录了,就能直接访 ...

  2. Spring Security构建Rest服务-1203-Spring Security OAuth开发APP认证框架之短信验证码登录

    浏览器模式下验证码存储策略 浏览器模式下,生成的短信验证码或者图形验证码是存在session里的,用户接收到验证码后携带过来做校验. APP模式下验证码存储策略 在app场景下里是没有cookie信息 ...

  3. Spring Security构建Rest服务-1201-Spring Security OAuth开发APP认证框架之实现服务提供商

    实现服务提供商,就是要实现认证服务器.资源服务器. 现在做的都是app的东西,所以在app项目写代码  认证服务器: 新建 ImoocAuthenticationServerConfig 类,@Ena ...

  4. Spring Security构建Rest服务-1200-SpringSecurity OAuth开发APP认证框架

    基于服务器Session的认证方式: 前边说的用户名密码登录.短信登录.第三方登录,都是普通的登录,是基于服务器Session保存用户信息的登录方式.登录信息都是存在服务器的session(服务器的一 ...

  5. Spring Security构建Rest服务-1205-Spring Security OAuth开发APP认证框架之Token处理

    token处理之二使用JWT替换默认的token JWT(Json Web Token) 特点: 1,自包含:jwt token包含有意义的信息 spring security oauth默认生成的t ...

  6. Spring Security构建Rest服务-1204-Spring Security OAuth开发APP认证框架之Token处理

    token处理之一基本参数配置 处理token时间.存储策略,客户端配置等 以前的都是spring security oauth默认的token生成策略,token默认在org.springframe ...

  7. Spring Cloud构建微服务架构(三)消息总线

     注:此文不适合0基础学习者直接阅读,请先完整的将作者关于微服务的博文全部阅读一遍,如果还有疑问,可以再来阅读此文,地址:http://blog.csdn.net/sosfnima/article/d ...

  8. 构建微服务:Spring boot

    构建微服务:Spring boot 在上篇文章构建微服务:Spring boot 提高篇中简单介绍了一下spring data jpa的基础性使用,这篇文章将更加全面的介绍spring data jp ...

  9. Spring Cloud构建微服务架构(二)服务消费者

    Netflix Ribbon is an Inter Process Communication (IPC) cloud library. Ribbon primarily provides clie ...

随机推荐

  1. POJ 1458 Common Subsequence (DP+LCS,最长公共子序列)

    题意:给定两个字符串,让你找出它们之间最长公共子序列(LCS)的长度. 析:很明显是个DP,就是LCS,一点都没变.设两个序列分别为,A1,A2,...和B1,B2..,d(i, j)表示两个字符串L ...

  2. org.apache.cxf 官方骨架

    mvn archetype:generate -DarchetypeCatalog=remote 用 cxf 筛选结果 官方三大项目骨架 org.apache.cxf:cxf-http-basic ( ...

  3. 解决Sublime Text 3中文显示乱码和语法着色问题 等问题

    一:解决sublime中文乱码的问题 简单安装: 1.打开Sublime Text 3,按Ctrl+-打开控制行,复制粘贴以下python代码,然后回车运行. 2. 复制并粘贴如下代码: import ...

  4. session token防表单重提

    1.表单页面初始化前,先在session存入一个token值,随后把token存放在表单页面隐藏表单域内,开始初始化: 在表单页初始化前,调用ajax请求,在后台生成token,并返回至表单页 fun ...

  5. 在ANTMINER(阉割版BeagleBone Black)运行Debain

    开门见山,直入主题 咸鱼入手3块阉割ARM板,经过快递近6天运输到手,不过价格便宜 东西下面这样的(借了咸鱼的图): 发现这块板是阉割版的国外beagleboard.org型号为BeagleBone ...

  6. [ACM_动态规划] hdu1003 Max Sum [最大连续子串和]

    Problem Description Given a sequence a[1],a[2],a[3]......a[n], your job is to calculate the max sum ...

  7. rtmp官方标准规范详细解析

    标准规范学习: rtmp消息结构,包括几个部分: 时戳:4  byte,单位毫秒.超过最大值后会翻转. 长度:消息负载的长度. 类型ID:Type Id 一部分ID范围用于rtmp的控制信令.还有一部 ...

  8. C#实现在图片上动态写内容

    之前在项目上遇到这么一个需求,就是要在图片上写内容,而且要求是动态,我所谓的动态就是在图片上写的内容是动态的.网上找了找,很多人实现了网图片上写内容的功能,但是,并没有实现动态.所以在这里把我的解决办 ...

  9. C#委托和事件的使用的意义

    转载自:https://www.cnblogs.com/yinqixin/p/5056307.html 每一个初学C#的程序猿,在刚刚碰到委托和事件的概念时,估计都是望而却步,茫然摸不到头脑的.百度一 ...

  10. C#不用union,而是有更好的方式实现

    用过C/C++的人都知道有个union,特别好用,似乎char数组到short,int,float等的转换无所不能,也确实是能,并且用起来十分方便.那C#为什么没有这个关键字呢?怎么实现这个功能?其实 ...