使用OAuth2的SSO分析

参考:https://github.com/spring-guides/tut-spring-security-and-angular-js/blob/master/oauth2-vanilla/README.adoc 

1.浏览器向UI服务器点击触发要求安全认证 
2.跳转到授权服务器获取授权许可码 
3.从授权服务器带授权许可码跳回来 
4.UI服务器向授权服务器获取AccessToken 
5.返回AccessToken到UI服务器 
6.发出/resource请求到UI服务器 
7.UI服务器将/resource请求转发到Resource服务器 
8.Resource服务器要求安全验证,于是直接从授权服务器获取认证授权信息进行判断后(最后会响应给UI服务器,UI服务器再响应给浏览中器)

一.先创建OAuth2授权服务器 
1.使用spring Initializrt生成初始项目,选使用spring boot 1.3.3生成maven项目,根据需要填写group,artifact,依赖选Web和Security两块,点生成按钮即可. 
2.加入OAuth2依赖到pom.xml

<dependency>
   <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

修改主类(这里同时也作为资源服务器).

@SpringBootApplication
@RestController
@EnableAuthorizationServer
@EnableResourceServer
public class AuthserverApplication {

    @RequestMapping("/user")
    public Principal user(Principal user) {
        return user;
    }

    public static void main(String[] args) {
        SpringApplication.run(AuthserverApplication.class, args);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

同时修改servlet容器的port,contextPath,注册一个测试用户与客户端,加入配置:application.properties

server.port: 9999
server.contextPath: /uaa
security.user.password: password
security.sessions: if-required
security.oauth2.client.clientId: acme
security.oauth2.client.clientSecret: acmesecret
security.oauth2.client.authorized-grant-types: authorization_code,refresh_token,password
security.oauth2.client.scope: openid
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

基于spring boot的security的session创建策略默认是STATELESS,至于几个选项意义,可看org.springframework.security.config.http.SessionCreationPolicy 
启动授权服务器后,可测试了: 
a.打开浏览器输入地址http://localhost:9999/uaa/oauth/authorize?response_type=code&client_id=acme&redirect_uri=http://example.com发出请求,然后根据以上配置,输入用户名/密码,点同意,获取返回的授权许可码 
b.在Linux的bash或mac的terminal输入 
[[email protected] ~]#curl acme:[email protected]:9999/uaa/oauth/token \ 
-d grant_type=authorization_code -d client_id=acme \ 
-d redirect_uri=http://example.com -d code=fjRdsL 
回车获取access token,其中fjRdsL替换上步获取的授权许可码.返回结果类似如下: 
{"access_token":"8eded27d-b849-4473-8b2d-49ae49e17943","token_type":"bearer","refresh_token":"5e9af75c-c442-433f-81ba-996eb2c00f53","expires_in":43199,"scope":"openid"}
从返回结果复制access_token,继续: 
[[email protected] ~]# TOKEN=8eded27d-b849-4473-8b2d-49ae49e17943 
[[email protected] ~]# curl -H “Authorization: Bearer $TOKEN” 192.168.1.115:9999/uaa/user 
其中上面的8eded27d-b849-4473-8b2d-49ae49e17943是access_token,根据实际情况替换,第二个命令返回结果类似如下: 
{"details":{"remoteAddress":"192.168.1.194","sessionId":null,"tokenValue":"8eded27d-b849-4473-8b2d-49ae49e17943","tokenType":"Bearer","decodedDetails":null},"authorities":[{"authority":"ROLE_USER"}],"authenticated":true,"userAuthentication":{"details":{"remoteAddress":"0:0:0:0:0:0:0:1","sessionId":"3943F6861E0FE31C29568542730342F6"},"authorities":[{"authority":"ROLE_USER"}],"authenticated":true,"principal":{"password":null,"username":"user","authorities":[{"authority":"ROLE_USER"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true,"enabled":true},"credentials":null,"name":"user"},"oauth2Request":{"clientId":"acme","scope":["openid"],"requestParameters":{"response_type":"code","redirect_uri":"http://example.com","code":"QzbdLe","grant_type":"authorization_code","client_id":"acme"},"resourceIds":[],"authorities":[{"authority":"ROLE_USER"}],"approved":true,"refresh":false,"redirectUri":"http://example.com","responseTypes":["code"],"extensions":{},"grantType":"authorization_code","refreshTokenRequest":null},"credentials":"","principal":{"password":null,"username":"user","authorities":[{"authority":"ROLE_USER"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true,"enabled":true},"clientOnly":false,"name":"user"}
从结果来看,使用access token访问资源一切正常,说明授权服务器没问题.

二.再看分离的资源服务器(改动也不少) 
不再使用SpringSession从Redis抽取认证授权信息,而是使用ResourceServerTokenServices向授权服务器发送请求获取认证授权信息.因些没用到SpringSession时可移除,同时application.properties配置security.oauth2.resource.userInfoUri或security.oauth2.resource.tokenInfoUri中的一个,主类修改如下:

@SpringBootApplication
@RestController
@EnableResourceServer
public class ResourceApplication {
    @RequestMapping("/")
    public Message home() {
        return new Message("Hello World");
    }
    public static void main(String[] args) {
        SpringApplication.run(ResourceApplication.class, args);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

最后运行主类的main方法,开始测试(授权服务器前面启动了,access_token也得到了),于是在使用curl命令: 
[[email protected] ~]# curl -H “Authorization: Bearer $TOKEN” 192.168.1.115:9000 
返回结果类似如下: 
{"id":"03af8be3-2fc3-4d75-acf7-c484d9cf32b1","content":"Hello World"} 
可借鉴的经验,我在windows上开发,启动资源服务器,然后资源服务器有配置server.address: 127.0.0.1,这里限制容器只能是本机访问,如果使用局域网IP是不可以访问的,比如你在别人的机器或在一台虚拟的linux上使用curl都是不是访问的,注释这行配置,这限制就解除. 
跟踪下获取认证授权的信息过程: 
1.userInfoRestTemplate Bean的声明在org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration.UserInfoRestTemplateConfiguration#userInfoRestTemplate 
2.使用前面配置的userInfoUri和上面的userInfoRestTemplate Bean在org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration.RemoteTokenServicesConfiguration.UserInfoTokenServicesConfiguration#userInfoTokenServices创建UserInfoTokenServices Bean. 
3.在org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer#configure添加了org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter 
4.当使用curl -H “Authorization: Bearer $TOKEN” 192.168.1.115:9000发出请求时,直到被OAuth2AuthenticationProcessingFilter拦截器处理, 
org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#doFilter{ 
Authentication authentication = tokenExtractor.extract(request);//抽取Token 
Authentication authResult = authenticationManager.authenticate(authentication);//还原解码认证授权信息 

org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager#authenticate{ 
OAuth2Authentication auth = tokenServices.loadAuthentication(token);//这里的tokenServices就是上面的UserInfoTokenServices Bean,就在这里向授权服务器发出请求. 

三.UI服务器作为SSO的客户端. 
1.同样UI服务器不需要springSession,认证如我们所期望的,交给授权服务器,所以使用Spring Security OAuth2依赖替换Spring Session和Redis依赖. 
2.当然UI服务器还是API网关的角色,所以不要移除@EnableZuulProxy.在UI服务器主类加上@EnableOAuth2Sso,这个注解会帮我们完成跳转到授权服务器,当然要些配置application.yml

zuul:
  routes:
    resource:
      path: /resource/**
      url: http://localhost:9000
    user:
      path: /user/**
      url: http://localhost:9999/uaa/user
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里将”/user”请求代理到授权服务器 
3.继续修改UI主类继承WebSecurityConfigurerAdapter,重写org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#configure(org.springframework.security.config.annotation.web.builders.HttpSecurity) 
目的是为了修改@EnableOAuth2Sso引起的默认Filter链,默认是org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2SsoDefaultConfiguration#configure,这个类上面有@Conditional(NeedsWebSecurityCondition.class)意思应该是,没有WebSecurityConfigurerAdapter才会去执行这个config,因为继承了这个类,所以此config不再执行. 
4.作为oauth2的客户端,application.yml下面这几项是少不了的

security:
  oauth2:
    client:
      accessTokenUri: http://localhost:9999/uaa/oauth/token
      userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize
      clientId: acme
      clientSecret: acmesecret
    resource:
      userInfoUri: http://localhost:9999/uaa/user
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

最后一项,因为也作为资源服务器,所以也加上吧.

spring:
  aop:
    proxy-target-class: true
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

spring aop默认一般都是使用jdk生成代理,前提是要有接口,cglib生成代理,目标类不能是final类,这是最基本的条件.估计是那些restTemplate没有实现接口,所以不得不在这里使用cglib生成代理. 
5.其它的前端微小改变,这里不赘述.把授权服务器,分离的资源服务器和这个UI服务器都启动.准备测试:http://localhost:8080/login 
a.经过security的拦截链接中的org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.doFilter拦截,触发了attemptAuthentication方法

    public OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException {
        OAuth2AccessToken accessToken = context.getAccessToken();
        if (accessToken == null || accessToken.isExpired()) {
            try {
                accessToken = acquireAccessToken(context);
            }catch (UserRedirectRequiredException e) {
                context.setAccessToken(null); // No point hanging onto it now
                accessToken = null;
                String stateKey = e.getStateKey();
                if (stateKey != null) {
                    Object stateToPreserve = e.getStateToPreserve();
                    if (stateToPreserve == null) {
                        stateToPreserve = "NONE";
                    }
                    context.setPreservedState(stateKey, stateToPreserve);
                }
                throw e;
            }
        }
        return accessToken;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

acquireAccessToken(context)去获取token的时候触发抛异常.在org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider#getRedirectForAuthorization处理发送的url,最后这个UserRedirectRequiredException往上抛 
一直往上抛到org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter#doFilter

    catch (Exception ex) {
        // Try to extract a SpringSecurityException from the stacktrace
        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
        UserRedirectRequiredException redirect = (UserRedirectRequiredException) throwableAnalyzer
                .getFirstThrowableOfType(
                        UserRedirectRequiredException.class, causeChain);
        if (redirect != null) {
            redirectUser(redirect, request, response);
        } else {
            if (ex instanceof ServletException) {
                throw (ServletException) ex;
            }
            if (ex instanceof RuntimeException) {
                throw (RuntimeException) ex;
            }
            throw new NestedServletException("Unhandled exception", ex);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

终于看到redirectUser(redirect, request, response);进行跳转到授权服务器去了.

授权服务器跳回到UI服务器原来的地址(带回来授权许可码),再次被OAuth2ClientAuthenticationProcessingFilter拦截发送获取accessToken,经org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport#retrieveToken提交POST请求,获取到返回原来发请求处得到OAuth2AccessToken对象. 
在org.springframework.security.oauth2.client.OAuth2RestTemplate#acquireAccessToken使用oauth2Context.setAccessToken(accessToken);对token进行保存.有了accessToken,就可以从授权服务器获取用户信息了.

最后,当用户点logout的时候,授权服务器根本没有退出(销毁认证授权信息)

http://blog.csdn.net/xiejx618/article/details/51039653

时间: 2024-11-05 22:53:20

使用OAuth2的SSO分析的相关文章

使用JWT的OAuth2的SSO分析

参考:https://github.com/spring-guides/tut-spring-security-and-angular-js/blob/master/oauth2/README.adoc http://jwt.io/introduction/ 本文在<使用OAuth2的SSO分析>文章的基础上扩展,使用jwt可减少了向认证服务器的请求,但jwt比swt(Simple Web Tokens)要长不少,还要依赖公钥解密. 1.浏览器向UI服务器点击触发要求安全认证 2.跳转到授权服

OAuth2.0学习(4-1)Spring Security OAuth2.0 - 代码分析

1.org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter              org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter              org.springframework.security.oauth2.client.fil

spring oauth2相关资料

理解OAuth 2.0  *****http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html Secure REST API with oauth2 (翻译) *****http://blog.csdn.net/haiyan_qi/article/details/52384734 https://spring.io/guides/tutorials/spring-boot-oauth2/The OAuth 2.0 Authorization Fr

C# .net用法大全

从事多年的开发,对于.net可以说有一定的总结,有关于教科书般的文档,献于交流. 本文整理了当前企业web开发中的管理系统,商城等系统的常用开发技术栈. C#常见运算符 一元运算符(+.-.!.~.++.--) 算术运算符(*./.%.+ . – ) 移位运算符(<< .>> ) 关系和类型测试运算符(==.!=.<.>.<=.>=.is 和 as) 逻辑运算符(&.^ 和 | ) 条件逻辑运算符(&& 和 || ) 空合并运算符(?

Floorplan_Editor学习记录

Floorplan Editor 概述 包含一些PACE.Floorplanner和FPGA Editor操作设置,主要做引脚指定.布局区域设定,10.1版本的主要仅支持V4/V5/Spartan 3A.作为图形交互应用,处理UCF文件,其功能包括: 查看和编辑I/O约束.通用逻辑(BRAM.时钟逻辑等) 查看和创建设计中逻辑的区域和位置约束 确立设计中的资源需求量 确立目标器件的资源布局 相关文件和要点 输入文件 NGD        设计本地化网表文件 UCF        用户约束原始文件

Spring Boot 实战

原文引用https://www.dazhuanlan.com/2019/08/26/5d630071b1ce1/ 打开google 打开springboot资料 Search or jump to- Pull requests Issues Marketplace Explore @shandudu 59 1,084 316 hansonwang99/Spring-Boot-In-Action Code Issues 0 Pull requests 0 Projects 0 Wiki Secur

Spring Security + JWT学习

开胃:Oauth2认证流程分析 现在第三方登录已经很普遍了,随便哪个App都会有使用微信登录,使用手机号码登录,或者使用支付宝登录等功能... 下面我们就以使用微信登录,做一个简单的流程分析分析 开胃:JWT认识 在上面的Oauth2的认证流程中,我们就可以看出一些猫腻来: 在我们拿着令牌去用户信息系统调用用户的相关信息的时候, 用户信息系统其实去请求了授权服务器,验证l了该令牌的合法性 这样每次认证都需要去调用授权服务器做一个令牌合法性验证,显得效率低下. JWT令牌思想 JWT令牌场景运用

基于Swift语言开发微信、QQ和微博的SSO授权登录代码分析

前言 Swift 语言,怎么说呢,有一种先接受后排斥,又欢迎的感觉,纵观国外大牛开源框架或项目演示,Swift几乎占据了多半,而国内虽然出现很多相关技术介绍和教程,但是在真正项目开发中使用的占据很少部分,原因一是目前熟练它的开发者并不多,二是版本不太稳定,还需要更成熟可靠的版本支持,但总之未来还是很有前景的,深有体会,不管是代码量还是编译效率,以及语言特性,现代性都优于Object-C,估计后续会被苹果作为官方开发语言,值得期待. 走起 鉴于此,笔者将之前用Object-C写的SSO授权登录:微

基于OAuth2.0协议的QQ第三方授权登录iOS代码分析

简要说明: 授权登录已经成为注册方式里的主流,目前授权登录方式主要SSO跳转授权登录和OAuth2.0两种,前者好处无需用户再次输入密码就可以直接授权成功,但前提是必须用户手机端安装了该软件,比如QQ,后者的优势就是是否安装无关紧要,是一个HTML的页面呈现,麻烦就在于要输入用户名和密码,这就非常不爽了,但是有时候偏偏必须这么做,理由嘛,自行想想就好,接下来我们就看看如果利用OAuth2.0的方式来做QQ授权登录,如果想了解QQ的SSO授权登录,可以看我(博客主页)之前的博客:基于第三方QQ授权