⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://niocoder.com/2018/01/25/Spring-Security源码分析十二-Spring-Security-OAuth2基于JWT实现单点登录/ 「龙飞」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指,只需要单一的注销动作,就可以结束对于多个系统的访问权限。

1. Security OAuth2 单点登录流程示意图

https://raw.githubusercontent.com/longfeizheng/longfeizheng.github.io/master/images/security/SpringSecurity-OAuth2-sso.png

  1. 访问client1
  2. client1将请求导向sso-server
  3. 同意授权
  4. 携带授权码code返回client1
  5. client1拿着授权码请求令牌
  6. 返回JWT令牌
  7. client1解析令牌并登录
  8. client1访问client2
  9. client2将请求导向sso-server
  10. 同意授权
  11. 携带授权码code返回client2
  12. client2拿着授权码请求令牌
  13. 返回JWT令牌
  14. client2解析令牌并登录

用户的登录状态是由sso-server认证中心来保存的,登录界面和账号密码的验证也是sso-server认证中心来做的(client1clien2返回token是不同的,但解析出来的用户信息是同一个用户)。

2. Security OAuth2 实现单点登录

2.1 项目结构

https://raw.githubusercontent.com/longfeizheng/longfeizheng.github.io/master/images/security/spring-security-oauth2-sso01.png

2.2 sso-server

2.2.1 认证服务器

@Configuration
@EnableAuthorizationServer
public class SsoAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

/**
* 客户端一些配置
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("merryyou1")
.secret("merryyousecrect1")
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("all")
.and()
.withClient("merryyou2")
.secret("merryyousecrect2")
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("all");
}

/**
* 配置jwttokenStore
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter());
}

/**
* springSecurity 授权表达式,访问merryyou tokenkey时需要经过认证
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("isAuthenticated()");
}

/**
* JWTtokenStore
* @return
*/
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}

/**
* 生成JTW token
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("merryyou");
return converter;
}
}

2.2.2 security配置

@Configuration
public class SsoSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/authentication/require")
.loginProcessingUrl("/authentication/form")
.and().authorizeRequests()
.antMatchers("/authentication/require",
"/authentication/form",
"/**/*.js",
"/**/*.css",
"/**/*.jpg",
"/**/*.png",
"/**/*.woff2"
)
.permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable();
// http.formLogin().and().authorizeRequests().anyRequest().authenticated();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}

2.2.3 SsoUserDetailsService

@Component
public class SsoUserDetailsService implements UserDetailsService {

@Autowired
private PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}

2.2.4 application.yml

server:
port: 8082
context-path: /uaa
spring:
freemarker:
allow-request-override: false
allow-session-override: false
cache: true
charset: UTF-8
check-template-location: true
content-type: text/html
enabled: true
expose-request-attributes: false
expose-session-attributes: false
expose-spring-macro-helpers: true
prefer-file-system-access: true
suffix: .ftl
template-loader-path: classpath:/templates/

2.3 sso-client1

2.3.1 SsoClient1Application

@SpringBootApplication
@RestController
@EnableOAuth2Sso
public class SsoClient1Application {

@GetMapping("/user")
public Authentication user(Authentication user) {
return user;
}

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

2.3.2 application.yml

auth-server: http://localhost:8082/uaa # sso-server地址
server:
context-path: /client1
port: 8083
security:
oauth2:
client:
client-id: merryyou1
client-secret: merryyousecrect1
user-authorization-uri: ${auth-server}/oauth/authorize #请求认证的地址
access-token-uri: ${auth-server}/oauth/token #请求令牌的地址
resource:
jwt:
key-uri: ${auth-server}/oauth/token_key #解析jwt令牌所需要密钥的地址

2.4 sso-client2

2.4.1 同sso-client1一致

效果如下: https://raw.githubusercontent.com/longfeizheng/longfeizheng.github.io/master/images/security/spring-security-oauth2-sso01.gif

3. 代码下载

从我的 github 中下载,https://github.com/longfeizheng/sso-merryyou

文章目录
  1. 1. 1. Security OAuth2 单点登录流程示意图
  2. 2. 2. Security OAuth2 实现单点登录
    1. 2.1. 2.1 项目结构
    2. 2.2. 2.2 sso-server
      1. 2.2.1. 2.2.1 认证服务器
      2. 2.2.2. 2.2.2 security配置
      3. 2.2.3. 2.2.3 SsoUserDetailsService
      4. 2.2.4. 2.2.4 application.yml
    3. 2.3. 2.3 sso-client1
      1. 2.3.1. 2.3.1 SsoClient1Application
      2. 2.3.2. 2.3.2 application.yml
    4. 2.4. 2.4 sso-client2
      1. 2.4.1. 2.4.1 同sso-client1一致
  3. 3. 3. 代码下载