概述:
最近闲来无事,就决定学习研究下我们公司微服务中的sso单点登录系统,经过详细阅读公司项目代码后了解到项目的sso单点登录系统是基于springsecurity+oauth2+jwt实现,所以决定在网上找一下有关sping-security的资料和视频进行学习,本篇文章只讲述搭建的过程以及具体实现步骤,至于具体的理论知识以及本篇博客就不在此介绍,比如oauth2协议,授权码模式,jwt令牌,security基础知识,本篇博客仅用于自己日常学习过程中的笔记
一.什么是SSO单点登录登录
我理解是通常微服务项目中有多个子系统(一个功能模块对应一个子系统),当用户在一个子系统登录之后,如果用户再切换其他的子系统那不可能再让用户进行登录操作吧,如果每次切换都要用户重新登录那么就会极大影响用户的体验,所以我们就需要sso单点登录系统来做统一的登录授权操作,一次授权处处信任,不管你的微服务有多少个子服务只要成功登录过一次,那么其他系统就不需要重新进行登录操作用户照样可以进行访问
二.什么是授权码模式
授权码模式只是oauth2协议里面的认证模式的其中一种,比如我们平常登录第三方网站的时候都会有一个QQ或者微信登录,当我选择使用微信登录该网站的时候,通过扫码授权登录第三方网站就会将此请求重定向到微信的认证服务器里面,如果用户确定授权那么微信的认证服务器就会完成认证将访问令牌和刷新令牌的信息反馈给第三方网站,第三方网站就会拿微信认证服务器反馈回来的访问令牌去资源服务器访问有限的资源,这里就不展开来说了....大概的认证流程如下图所示
三.搭建SSO单点登录系统
1.建一个springsecurity-oauth2maven项目,里面有三个springsecurity-sso模块代表三个子系统
2.引入相关的pom.xml依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<modules>
<module>springsecurity-sso</module>
<module>springsecurity-sso2</module>
<module>springsecurity-sso3</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.13.RELEASE</version>
<relativePath/>
</parent>
<groupId>org.example</groupId>
<artifactId>springsecurity-oauth2</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<spring.cloud.version>Greenwich.SR2</spring.cloud.version>
</properties>
<dependencies>
<!-- spring-cloud-starter-oauth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!-- spring-cloud-starter-security -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<!--web组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- spring-boot-starter-data-redis -->
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>-->
<!-- commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
<!-- jwt组件 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.首先编写启动类SpringSecurityOauthApp
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringSecurityOauthApp {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityOauthApp.class);
}
}
4.实现自定义的登录逻辑,不使用springsecurity自带的登录逻辑
5.springsecurity配置,包括一些登录url,拦截哪些请求,密码加密器,授权管理器,关闭csrf防护,哪些请求需要放行之类的,详细的配置如下
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/**","/login/**","/logout/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
}
/*
* 密码加密器
* */
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/*
* 授权管理器
* */
@Override
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
6.认证授权服务器配置,主要是对于第三方客户端配置,秘钥,授权成功之后的重定向url ,授权范围,令牌刷新时间,令牌失效时间,授权类型,是否自动授权等信息,注意记得加上@EnableAuthorizationServer这个注解(很重要)
public class AuthorizeServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
/*
* 这个客户端代表着访问我们授权服务器的第三方应用程序
* */
//指定令牌的存储策略为redi或inMemory内存
clients.inMemory()
//客户端id
.withClient("client")
//秘钥
.secret(passwordEncoder.encode("ihyx"))
//重定向地址
// .redirectUris("http://www.baidu.com")
.redirectUris("http://localhost:8081/login","http://localhost:8082/login","http://localhost:8083/login") //sso单点登录系统演示
//授权范围
.scopes("all")
//设置访问令牌失效时间
.accessTokenValiditySeconds(60)
//设置刷新令牌失效时间
.refreshTokenValiditySeconds(1200)
//自动授权
.autoApprove(true)
/*
* 授权类型
* authorization_code:授权码类型
* password:密码类型
* refresh_token:刷新令牌
*
* */
.authorizedGrantTypes("authorization_code","password","refresh_token");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
//指定令牌存储策略
//.tokenStore(tokenStore)
//使用jwt令牌存储策略
.tokenStore(tokenStore)
//accessToken转成jwtToken
.accessTokenConverter(jwtAccessTokenConverter);
}
//SSO单点登录系统额外配置
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//获取秘钥必须要身份验证 单点登录必须要配置
security.tokenKeyAccess("isAuthenticated()");
}
7.资源服务器配置,配置需要放行的资源,注意加上@EnableResourceServer注解,目前该资源服务器上只有一个请求当前用户信息的请求表示一个资源
@Configuration
@EnableResourceServer
/*
* 资源服务器配置
* */
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
/*
* 配置资源放行规则
* */
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.requestMatchers()
.antMatchers("/user/**");
}
}
@RestController
@RequestMapping("/user")
public class UserController {
/*
* 获取当前用户信息
* */
@RequestMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication, HttpServletRequest request){
//return authentication.getPrincipal();
String header = request.getHeader("Authorization");
//获取jwtToken
String token = header.substring(header.lastIndexOf("bearer") + 7);
//解析JwtToken
Object ihyx = Jwts.parser().
//设置秘钥
setSigningKey("ihyx".getBytes(Charset.forName("utf8")))
.parse(token).getBody();
return ihyx;
}
}
8.配置jwt令牌存储机制
@Configuration
public class JwtStoreConfig {
@Bean
public TokenStore tokenStore(){
TokenStore tokenStore=new JwtTokenStore(jwtAccessTokenConverter());
return tokenStore;
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
//设置jwt秘钥
jwtAccessTokenConverter.setSigningKey("ihyx");
return jwtAccessTokenConverter;
}
}
9.子系统搭建,配置授权服务器地址以及获取token,授权码的url,然后子系统就一个获取当前用户信息的请求,必须登录之后才能请求否则的话就跳转到单点登录系统进行登录,其他的两个也是这样配置只是cookie-name不一样以及server.port
server.port=8082
#防止cookie冲突
server.servlet.session.cookie.name=springsecurity-sso-session02
#授权服务器地址
oauth.server.url=http://localhost:8080
#与授权服务器对应的配置
#客户端id
security.oauth2.client.client-id=client
#秘钥
security.oauth2.client.client-secret=ihyx
#认证url 获取授权码
security.oauth2.client.user-authorization-uri=${oauth.server.url}/oauth/authorize
#获取token
security.oauth2.client.access-token-uri=${oauth.server.url}/oauth/token
#获取jwt令牌
security.oauth2.resource.jwt.key-uri=${oauth.server.url}/oauth/token_key
@RestController
@RequestMapping("/app")
public class AppController {
@RequestMapping("/getUserInfo")
public Object getUserInfo(Authentication authentication){
return authentication.getPrincipal();
}
}
四.验证单点登录是否符合预期
三个系统的getUserInfo请求是一定要登录之后才能获取,如果当前用户没有登录就访问的话会直接跳转到登录系统,然后输入用户名和密码,系统1登录成功之后,查看系统2,系统3访问同样的请求看下是否还需要登录,如果不需要登录就能访问证明搭建成功
1.启动四个project
2.依次访问localhost:8081/app/getUserInfo,localhost:8082/app/getUserInfo,localhost:8083/app/getUserInfo
3.在系统1登录,然后依次再请求localhost:8081/app/getUserInfo,localhost:8082/app/getUserInfo,localhost:8083/app/getUserInfo
五.总结
整套sso单点系统搭建起来还是蛮困难,当然是对于我这种小白来说吧,在搭建的过程也遇到比较难理解抽象的理论概念,比如oauth2的几个角色,客户端,认证授权服务器,资源服务器以及还有很多关于oauth2的理论等等,下去之后得要好好补充一下这方面的知识咯...