一、介绍
1、相关概念
首先,要说明使用之前,我们来了解一下oauth2提供的几个注解相关功能:
- @EnableOAuth2Client
客户端,提供OAuth2RestTemplate,用于客户端访问资源服务。
简要步骤:客户端访问资源->客户端发现没有资源访问token->客户端根据授权类型生成跳转url->浏览器 302 到认证授权服务进行认证、授权。 - @EnableOAuth2Sso
应用系统,使用远端认证授权服务,替换应用自身的用户登录鉴权security逻辑,实现单点登录功能。
简要步骤:访问应用系统资源-> 应用系统发现未登录-> 302 跳转到登录页面(登录页面地址已经与获取token逻辑自动关联)-> 应用系统发现符合获取token条件,根据授权类型拼装url->302 跳转到认证授权地址(认证授权服务提供)进行认证、授权。 - @EnableAuthorizationServer
认证授权服务,提供用于获取token,解析token相关功能,实现认证、授权功能。
具体见 Spring Security 文章目录中的 Spring Cloud OAuth2 五种授权方式介绍。 - @EnableResourceServer
资源服务,提供基于token的资源访问功能。
所谓的单点登录(Single Sign On,简称SSO)是指许多个逻辑上相互独立的系统或软件构成一个完整的系统的平台中仅有唯一的授权系统,多个系统或软件使用授权系统进行统一认证和授权,对浏览器或客户端来说就是都是使用同一个授权系统进行登录授权,用户只需要登录一次就可以访问所有相互信任的应用系统。
根据以上概念,我们只需要专注两点:
- 只有一个授权系统(这里的一个是一类集群,可包含多个服务,如微服务中的多个授权服务)
- 用户不论开始访问哪个系统,只需要登录一次就可以到处访问
2、系统流程
理解SSO单点登录概念之后,我们就需要知道SSO是如何实现多个系统统一认证授权的,这里我找了一张URL时序图,如下所示:

这张图画的非常清晰,已经对整理流程描述的非常到位,这里我稍作解析,流程解析如下:
- 首先,浏览器访问系统1,由于没有登录则跳转到授权系统
- 授权系统发现该session用户并没有登录则返回授权系统的登录页面(可以自定义)
- 用户通过统一的登录页面输入用户名密码进行登录
- 授权系统进行认证(Provider)并创建令牌返回系统1
- 系统1通过设置的userAuthorizationUri到授权服务进行授权并携带自己的重定向url
- 授权服务校验令牌有效并返回系统1
- 系统1通过用户认证信息坚定是否具有该资源的访问权限,有则通过无则拒绝
当用户登录后再次访问系统2的流程如下:
- 用户访问系统2,系统2发现未登录跳转到授权系统
- 授权系统用户会话查找发现用户已登录,跳转到系统2并携带令牌
- 系统2到授权服务校验令牌有效性,验证成功后跳转到系统2
- 系统2验证用户权限,通过2允许访问受保护资源
二、实战实例
为了能说明整个系统的流程,我们必须要准备至少3个系统,它包括:
- 授权系统
进行所有服务的统一认证授权。 - 资源系统A
提供A资源的访问。 - 资源系统B
提供B资源的访问。
正如我的《oauth2资源服务》一文中看到的,资源服务要到认证服务获取用户认证信息最终是通过OAuth2RestOperations对象请求配置的tokenInfoUri或userInfoUri从授权服务获取的。对于单点登录配置原理也是一样,它也是通过一个对象OAuth2RestOperations的实现类实现OAuth2RestTemplate的,只不过,我们看不到里面的实现而已,至于里面的加载过程,我找到一张图,如下图所示:
至于代码的加载流程与《oauth2
资源服务》中的一致,感兴趣的可以根据我的思路一步一步端点跟踪,这里我就不再贴出代码来讲解加载过程中做了什么。
1、系统配置
(1)授权系统配置
首先,我们要准备基础的用户数据,用于用户登录,这里主要是注册客户端数据,包括客户端id、秘钥、授权模式、重定向地址列表、过期时间等,我的测试数据如下所示:
my_client_id
gate_way_server
$2a$10$9mmTWJd1pJ2OjWKG1G1pNuyUxIG6Lv8lic42VmBXYrVNG4ZB9FwL6
user_info
authorization_code,refresh_token,implicit,password,client_credentials
http://www.baidu.com,http://localhost:7001/demon/login,http://localhost:7001/A/login,http://localhost:7002/B/login
ROLE_ADMIN
7200
86400
{"systemInfo":"Atlas System"}
true

这里要注意redirect_url参数,它决定了授权码code回传到重定向的地址,对于子系统A和B来说,他们的重定向地址是http://xxxx/服务名/login; 所以这里填写了两个系统的地址,注意不要写错否则,报错:
Invalid redirect: http://localhost:7001/A/login does not match one of the registered values.

(2) 客户端服务
关于客户端,是我们提供给外部浏览器访问的前置服务,最终所有的权限验证我们通过SSO登录到授权服务进行验证。所以,我这里的两个子系统配置必须配置资源服务以及授权client相关信息(注意,我用的不是jwt加密模式),配置文件如下所示:
系统A配置:
#oauth2客户端
security:
oauth2:
resource:
filter-order: 3
id: resource_server_id
tokenInfoUri: http://127.0.0.1:7000/oauth/check_token
preferTokenInfo: true
#user-info-uri: http://127.0.0.1:7000/user/principal
#prefer-token-info: false
#如下可暂时不用配置-仅做保留
client:
accessTokenUri: http://127.0.0.1:7000/oauth/token
userAuthorizationUri: http://127.0.0.1:7000/oauth/authorize
clientId: my_client_id
clientSecret: my_client_secret
系统B配置:
#oauth2客户端
security:
oauth2:
resource:
filter-order: 3
id: resource_server_id
tokenInfoUri: http://127.0.0.1:7000/oauth/check_token
preferTokenInfo: true
#user-info-uri: http://127.0.0.1:7000/user/principal
#prefer-token-info: false
client:
accessTokenUri: http://127.0.0.1:7000/oauth/token
userAuthorizationUri: http://127.0.0.1:7000/oauth/authorize
clientId: my_client_id
clientSecret: my_client_secret
这里我说明一下对应参数,resource是资源服务相关参数,client是客户端先关参数,对应的地址都是授权服务提供的接口地址,各个接口含义通过《oauth2认证流程》和《oauth2资源服务》两篇文章大家都应该了解到。
初次之外,我们在客户端需要添加SSO相关包,引入SSO相关配置及类,所以以子系统的pom配置如下所示:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.easystudy</groupId>
<artifactId>SSOClient</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>SSO单点登录系统</name>
<modules>
<module>SystemA</module>
<module>SystemB</module>
</modules>
<!-- spring boot项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 项目依赖管理声明,统一管理项目依赖的版本信息,继承项目无需声明版本 -->
<dependencyManagement>
<!-- 依赖管理 -->
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 项目属性:子模块不能引用父项目的properties变量 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
<!-- <spring-cloud.version>Finchley.BUILD-SNAPSHOT</spring-cloud.version>-->
<lombok.version>1.16.20</lombok.version>
<spring-security-oauth2-autoconfigure.version>2.1.0.RELEASE</spring-security-oauth2-autoconfigure.version>
</properties>
<!-- 项目依赖:特殊强制依赖,其他继承父亲 -->
<dependencies>
<!-- 监控系统健康情况的工具 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--Lombok:消除模板代码-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- logback日志包 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 资源服务器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.4.RELEASE</version><!--$NO-MVN-MAN-VER$-->
</dependency>
<!--
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
-->
<!-- RESTFul接口文档:通过接口访问文档:http://localhost:8762/swagger-ui.html,8762为服务配置的监听端口,默认8080 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
<!-- 配置文件读取 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<!-- 编译插件 -->
<build>
<plugins>
<!--spring boot maven插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、代码实现
有了以上配置之后,我们就需要实现授权系统和客户端系统,我们首先来看客户端系统的安全配置:
package com.easystudy.config;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
/**
* @文件名称: WebSecurityConfig.java
* @功能描述: WebSecurity安全配置
* @版权信息: www.easystudy.com
* @技术交流: 961179337(QQ群)
* @编写作者: lixx2048@163.com
* @联系方式: 941415509(QQ)
* @开发日期: 2020年7月26日
* @备注信息: 任何需要身份验证的请求都将被重定向到授权服务器
*/
@Configuration
@EnableOAuth2Sso
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* @功能描述: 受保护资源访问策略配置
* @编写作者: lixx2048@163.com
* @开发日期: 2020年7月26日
* @历史版本: V1.0
* @参数说明:
* @返 回 值:
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 资源访问安全策略
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
registry
.anyRequest()
.authenticated()
// 跨域请求配置
.and()
.csrf().disable();
}
/**
* @功能描述: 静态资源忽略放行配置
* @编写作者: lixx2048@163.com
* @开发日期: 2020年7月26日
* @历史版本: V1.0
* @参数说明:
* @返 回 值:
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 放行静态资源,否则添加oauth2情况下无法显示
web.ignoring().antMatchers("/favor.ico", "/favicon.ico","/v2/api-docs", "/swagger-resources/configuration/ui",
"/swagger-resources","/swagger-resources/configuration/security",
"/swagger-ui.html","/css/**", "/js/**","/images/**", "/webjars/**", "**/favicon.ico", "/index");
}
}
我们做了两项配置,一个是资源访问权限配置,这里配置为authenticated,也就是所有资源都必须认证后才能访问。一个是静态资源访问,针对所有静态资源都忽略权限(放行);
接下来,就是授权服务器配置:
package com.easystudy.config;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import com.easystudy.oauth.UsernameUserDetailService;
/**
* @文件名称: AuthorizationServerConfig.java
* @功能描述: 授权服务配置类,配置token存储方式、clientDetail等
* @版权信息: www.easystudy.com
* @技术交流: 961179337(QQ群)
* @编写作者: lixx2048@163.com
* @个人信息:941415509
* @开发日期: 2020年7月27日
* @历史版本: V1.0
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private UsernameUserDetailService userDetailsService;
@Primary
@Bean
public JdbcTokenStore jdbcTokenStore(){
return new JdbcTokenStore(dataSource);
}
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
// 使用的认证管理器-默认包含登录认证、用户名密码认证
.authenticationManager(authenticationManager)
// token存储在数据库中-生产环境使用以免服务器崩溃
.tokenStore(jdbcTokenStore())
// 设置refresh token是否重复使用,若无,refresh_token会有UserDetailsService is required错误
.reuseRefreshTokens(false)
// 从数据查用户授权信息
.userDetailsService(userDetailsService)
// 授权请求提交方式
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
endpoints.tokenServices(defaultTokenServices());
}
@Primary
@Bean
public DefaultTokenServices defaultTokenServices(){
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(jdbcTokenStore()); // token存储在redis中-如果存储在jdbc中就需要建立token存储表
tokenServices.setSupportRefreshToken(true); // 支持更换token
tokenServices.setClientDetailsService(clientDetails()); // jdbc具体的秘钥认证服务-如果存储在jdbc中就需要建立oauth_client_details表
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(2)); // token有效期自定义设置,2小时
tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); // 1天之内可以刷新token
return tokenServices;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()");
security.checkTokenAccess("isAuthenticated()");
security.allowFormAuthenticationForClients();
}
}
这里我使用的jdbc存储token方式,授权服务的安全配置如下:
package com.easystudy.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import com.easystudy.oauth.UsernameUserDetailService;
/**
* @文件名称: WebSecurityConfig.java
* @功能描述: WebSecurity安全配置
* @版权信息: www.easystudy.com
* @技术交流: 961179337(QQ群)
* @编写作者: lixx2048@163.com
* @联系方式: 941415509(QQ)
* @开发日期: 2020年7月26日
* @历史版本: V1.0
*/
@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UsernameUserDetailService usernameUserDetailService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* @功能描述: 自定义认证管理器配置
* @编写作者: lixx2048@163.com
* @开发日期: 2020年7月26日
* @历史版本: V1.0
* @参数说明:
* @返 回 值:
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* @功能描述: 受保护资源访问策略配置
* @编写作者: lixx2048@163.com
* @开发日期: 2020年7月26日
* @历史版本: V1.0
* @参数说明:
* @返 回 值:
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 资源访问安全策略
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
registry
// 任何配置都需要登录认证
//.antMatchers("/oauth/user/me")
// .permitAll()
.anyRequest()
.authenticated()
// 登录地址配置以及登录成功默认主页配置
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")
.permitAll()
// 登出接口权限配置
.and()
.logout().permitAll()
// 跨域请求配置
.and()
.csrf().disable()
// 进行http Basic认证
.httpBasic();
}
/**
* @功能描述: 认证管理提供者列表配置
* @编写作者: lixx2048@163.com
* @开发日期: 2020年7月26日
* @历史版本: V1.0
* @参数说明:
* @返 回 值:
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
/**
* @功能描述: 数据库认证提供者配置
* @编写作者: lixx2048@163.com
* @开发日期: 2020年7月26日
* @历史版本: V1.0
* @参数说明:
* @返 回 值:
*/
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
// 设置userDetailsService
provider.setUserDetailsService(usernameUserDetailService);
// 禁止隐藏用户未找到异常
provider.setHideUserNotFoundExceptions(false);
// 使用BCrypt进行密码的hash
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
/**
* @功能描述: 认证失败地址配置
* @编写作者: lixx2048@163.com
* @开发日期: 2020年7月26日
* @历史版本: V1.0
* @参数说明:
* @返 回 值:
*/
@Bean
public AuthenticationFailureHandler simpleUrlAuthenticationFailureHandler() {
return new SimpleUrlAuthenticationFailureHandler("/login?error");
}
/**
* @功能描述: 静态资源忽略放行配置
* @编写作者: lixx2048@163.com
* @开发日期: 2020年7月26日
* @历史版本: V1.0
* @参数说明:
* @返 回 值:
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 放行静态资源,否则添加oauth2情况下无法显示
web.ignoring().antMatchers("/favor.ico", "/favicon.ico","/v2/api-docs", "/swagger-resources/configuration/ui",
"/swagger-resources","/swagger-resources/configuration/security",
"/swagger-ui.html","/css/**", "/js/**","/images/**", "/webjars/**", "**/favicon.ico", "/index");
}
}
此外,A系统提供受保护的资源接口,实现如下:
package com.easystudy.controller;
import java.security.Principal;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
/**@文件名称: TestController.java
* @功能描述: TODO(用一句话描述该文件做什么)
* @版权信息: www.easystudy.com
* @技术交流: 961179337(QQ群)
* @编写作者: lixx2048@163.com
* @联系方式: 941415509(QQ)
* @开发日期: 2020年7月27日
* @历史版本: V1.0
*/
@RestController
@RequestMapping("/test")
@Api(value = "OAuth2 Client测试接口文档", tags = "OAuth2 Client测试接口文档")
public class TestController {
// oauth2注解
/**
* @RequiresUser:subject.isRemembered()结果为true,subject.isAuthenticated()
* @RequiresAuthentication:同于方法subject.isAuthenticated() 结果为true时
* @RequiresGuest:与@RequiresUser完全相反。
* @RequiresRoles("xx");有xx角色才可以访问方法
* @RequiresPermissions({"file:read", "write:aFile.txt"} ):同时含有file:read和write:aFile.txt的权限才能执行方法
*/
@GetMapping("/hi")
@ApiOperation(value="打招呼1", notes="打招呼1")
@ApiImplicitParams({ @ApiImplicitParam(paramType = "query", dataType = "String", name = "name", value = "名称", required = true) })
public String hi(@RequestParam(name = "name", required = true) String name){
return "A系统:hi," + name;
}
/**
* @功能描述: 获取用户认证信息(已登录用户)
* @技术交流: 961179337(QQ群)
* @编写作者: lixx2048@163.com
* @联系方式: 941415509(QQ)
* @开发日期: 2020年7月31日
*/
@GetMapping("/info")
public Principal info(Principal principal) {
return principal;
}
@GetMapping("/me")
//@PreAuthorize("hasAuthority('member:save')")
public Authentication me(Authentication authentication) {
return authentication;
}
}
B系统提供受保护的资源接口,实现如下:
package com.easystudy.controller;
import java.security.Principal;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
/**@文件名称: TestController.java
* @功能描述: TODO(用一句话描述该文件做什么)
* @版权信息: www.easystudy.com
* @技术交流: 961179337(QQ群)
* @编写作者: lixx2048@163.com
* @联系方式: 941415509(QQ)
* @开发日期: 2020年7月27日
* @历史版本: V1.0
*/
@RestController
@RequestMapping("/test")
@Api(value = "OAuth2 Client测试接口文档", tags = "OAuth2 Client测试接口文档")
public class TestController {
// oauth2注解
/**
* @RequiresUser:subject.isRemembered()结果为true,subject.isAuthenticated()
* @RequiresAuthentication:同于方法subject.isAuthenticated() 结果为true时
* @RequiresGuest:与@RequiresUser完全相反。
* @RequiresRoles("xx");有xx角色才可以访问方法
* @RequiresPermissions({"file:read", "write:aFile.txt"} ):同时含有file:read和write:aFile.txt的权限才能执行方法
*/
@GetMapping("/hi")
@ApiOperation(value="打招呼1", notes="打招呼1")
@ApiImplicitParams({ @ApiImplicitParam(paramType = "query", dataType = "String", name = "name", value = "名称", required = true) })
public String hi(@RequestParam(name = "name", required = true) String name){
return "B系统:hi," + name;
}
/**
* @功能描述: 获取用户认证信息(已登录用户)
* @技术交流: 961179337(QQ群)
* @编写作者: lixx2048@163.com
* @联系方式: 941415509(QQ)
* @开发日期: 2020年7月31日
*/
@GetMapping("/info")
public Principal info(Principal principal) {
return principal;
}
@GetMapping("/me")
//@PreAuthorize("hasAuthority('member:save')")
public Authentication me(Authentication authentication) {
return authentication;
}
}
3、接口测试
测试目标和结果1:
1、浏览器访问受保护的系统A的接口:/test/hi?name=lixx会重定向到授权服务的登录页面;
2、用户输入用户名密码点击确认后,授权服务重定向到原来的地址/test/hi?name=lixx
3、系统A返回打印结果:“A系统:hi,lixx”
测试目标和结果2:
1、浏览器访问受保护的系统B的接口:/test/hi?name=lixx
2、系统B直接从授权服务返回对应token并自动验证通过并重定向到/test/hi?name=lixx,用户不在需要输入用户名密码;
3、系统B返回打印结果:“A系统:hi,lixx”
我们按照我们的测试目标和结果一步步测试:
浏览器访问A系统的接口
访问地址:
http://localhost:7001/A/test/hi?name=lixx
由于用户未登录,重定向授权服务器的登录页面:
http://localhost:7000/login

用户登录授权跳转系统A接口

浏览器访问B系统的接口
访问地址:
http://localhost:7002/B/test/hi?name=lixx
系统B直接返回接口数据(无需登录)

三、相关总结
单点登录实现原理同资源服务与授权服务差不多,只不过内部获取token的过程不太一样:
1、内部实现原理
单点登录使用的授权码模式重定向设定的地址,然后在通过code换取token;而资源服务是通过从授权服务获取自定义接口提供用户认证信息或通过token获取认证信息;
2、交互方式
单点登录针对的是浏览器客户端,有一个重定用户授权过程(授权码模式),需要用户输入用户名和密码进行干预;
资源服务则有两种方式:
- 从授权服务获取token,然后浏览器携带token访问资源服务,资源服务通过token获取用户认证信息然后进行权限校验;
- 将用户名密码(这里不是客户id和秘钥)编码后作为access_token访问资源服务,资源服务通过Authorization头获取用户信息访问授权服务获取用户认证信息然后进行权限校验;
3、使用场景
单点登录一般用户前端资源服务; 资源服务一般用于后台的接口服务;
以上是本人的自我总结,不对之处,欢迎各位提出宝贵意见!
源码获取、合作、技术交流请获取如下联系方式:
QQ交流群:961179337

微信账号:lixiang6153
公众号:IT技术快餐
电子邮箱:lixx2048@163.com
5985



被折叠的 条评论
为什么被折叠?



