SSO

基于 Spring Security OAuth2 SSO 单点登录系统

Posted by leone on 2018-04-03

Spring Security OAuth2 SSO 单点登录系统

SSO简介

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

Spring Security OAuth

Spring Security OAuth使用标准的Spring和Spring Security编程模型和配置惯例,为使用Spring Security with OAuth和OAuth2提供支持。OAuth2协议

案例介绍

此工程分为三个模块:授权服务器(sso-oauth-server)、web应用client1(sso-client1)、web应用client2(sso-client2),想达到的目的是:某一个用户在client1系统登陆后在跳往client2系统后不用在重复登录。

  • sso-auth-server: 授权服务器

    • pom:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    </dependency>
    </dependencies>
- yml:

1
2
3
server:
port: 8082
context-path: /auth_server
- SsoOAuthServerApplication.java
1
2
3
4
5
6
7
8
9
/**
* @author Leone
**/
@SpringBootApplication
public class SsoOAuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(SsoOAuthServerApplication.class, args);
}
}
- userDetailsService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author Leone
**/
@Component
public class SsoUserDetailsService implements UserDetailsService {

@Autowired
private PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, passwordEncoder.encode("admin"),
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}
- SsoSecurityConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @author Leone
**/
@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()
.and().authorizeRequests()
.antMatchers("/**/*.js", "/**/*.css", "/**/*.jpg", "/**/*.png")
.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());
}
}
- SsoAuthServerConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* @author Leone
**/
@Configuration
@EnableAuthorizationServer
public class SsoAuthServerConfig extends AuthorizationServerConfigurerAdapter {

/**
* 客户端一些配置
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client1")
.secret("secret1")
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("all", "read", "write")
.autoApprove(true)
.and()
.withClient("client2")
.secret("secret2")
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("all", "read", "write")
.autoApprove(true);
}

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

/**
* springSecurity 授权表达式
*
* @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("andy");
return converter;
}
}
  • sso-client1:web应用client1

    • pom:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    </dependency>

    </dependencies>
    • yml:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    server:
    port: 8080
    context-path: /clienta
    security:
    oauth2:
    client:
    clientId: client1
    clientSecret: secret1
    # 请求令牌的地址
    access-token-uri: http://127.0.0.1:8082/auth_server/oauth/token
    # 请求认证的地址
    user-authorization-uri: http://127.0.0.1:8082/auth_server/oauth/authorize
    resource:
    jwt:
    # 解析jwt令牌所需要密钥的地址
    key-uri: http://127.0.0.1:8082/auth_server/oauth/token_key
    • index.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>sso-client-A</title>
    </head>
    <body>
    <h1>sso demo client-A</h1>
    <a href="http://127.0.0.1:8081/clientb/index.html">访问client-b</a>
    </body>
    </html>
    • SsoClient1.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * @author Leone
    **/
    @EnableOAuth2Sso
    @SpringBootApplication
    public class SsoClient1 {
    public static void main(String[] args) {
    SpringApplication.run(SsoClientA.class, args);
    }
    }
  • sso-client2:web应用client2

    • pom:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    </dependency>

    </dependencies>
    • yml:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server:
    port: 8081
    context-path: /clientb
    security:
    oauth2:
    client:
    clientId: client2
    clientSecret: secret2
    access-token-uri: http://127.0.0.1:8082/auth_server/oauth/token
    user-authorization-uri: http://127.0.0.1:8082/auth_server/oauth/authorize
    resource:
    jwt:
    key-uri: http://127.0.0.1:8082/auth_server/oauth/token_key
    • index.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>sso-client-B</title>
    </head>
    <body>
    <h1>sso demo client-B</h1>
    <a href="http://127.0.0.1:8080/clienta/index.html">访问client-a</a>
    </body>
    </html>
    • SsoClientA.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    * @author Leone
    **/
    @RestController
    @EnableOAuth2Sso
    @SpringBootApplication
    public class SsoClientB {

    @Autowired
    private OAuth2RestTemplate oAuth2RestTemplate;

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

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

    @Bean
    public OAuth2RestTemplate oAuth2RestTemplate(
    OAuth2ClientContext oAuth2ClientContext, OAuth2ProtectedResourceDetails details){
    return new OAuth2RestTemplate(details,oAuth2ClientContext);
    }
    }

总结

当访问client1的index.html页面的时候,会跳转到一个验证用户身份的页面,当用户输入正确的用户名和密码后就会跳转到原始请求的页面,这样在client1中跳转到client2的时候就不用再次输入用户名和密码登录,这样就达到了Single Sign On的效果。