尝试通过Spring Security + OAuth2实现单点登录(SSO)功能

今天我将通过制作示例来解释一下单点登录(SSO)的概念。

SSO是什么意思?

SSO流程图.png

SS0功能实现

本文将创建一个示例项目来解释。主要利用Spring Security Oauth2来实现单点登录功能。

环境准备完成

如果你已经具备Spring Security、OAuth2.0等方面的知识,就很好了。

    • Eclipse

 

    • Java8

 

    • Spring

 

    OAuth2.0

建立亲自项目

我用Maven创建了一个父项目。将其命名为sso-oauth2-demo。
我将其放置在pom.xml中如下所示。

<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.example</groupId>
    <artifactId>sso-oauth2-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <spring-boot.version>2.1.9.RELEASE</spring-boot.version>
        <spring-security.version>2.1.9.RELEASE</spring-security.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>${spring-security.version}</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

制作单一登录认证服务器

配置SSO认证服务器

在上述的主项目下创建子模块,并将其命名为auth-server。将以下内容放置在auth-server项目的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>sso-oauth2-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <artifactId>auth-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>auth-server</name>
    <description>Demo project for Spring Boot</description>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

创建一个application.yml文件,并按照下面的方式进行配置。
在此处,我们指定了SSO认证服务器的端口(8300)。

server:
  port: 8300
  servlet:
    context-path: '/auth'
单点登录认证服务器的实现

(1). 实现main类
按照以下方式进行实现:
在main类上方添加@EnableResourceServer注解。此注解非常重要。

@SpringBootApplication
@EnableResourceServer
public class  AuthServerApplication 
{
    public static void main( String[] args )
    {
     SpringApplication.run(AuthServerApplication.class, args);
    }
}

(2) 实现 auth 类


/**
 * SSO認証サーバー
 * @author hyman
 */
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll")
                .checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("SampleClientId")
                .secret(passwordEncoder.encode("secret"))
                .authorizedGrantTypes("authorization_code")
                .scopes("user_info")
                .autoApprove(true)
                .redirectUris("http://localhost:8301/login", "http://localhost:8302/login");
    }


}

(3). 实现Security类
在这里,用户id和密码将在源代码中进行定义。

用户ID: hyman
密码: hyman123


/**
 * security配置
 * ここでユーザidとパスワードはソースの中で定義します。
 * @author hyman
 *
 */
@Configuration
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers()
                .antMatchers("/login", "/oauth/authorize")
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and().csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("hyman") // ユーザーID:hyman
                .password(passwordEncoder().encode("hyman123"))  // パスワード:hyman123
                .roles("USER");
    }

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

(3). 实现SSO认证API类
访问SSO认证服务器的URL为http://localhost:8300/auth/user/me。

@RestController
@RequestMapping(value = "user")
public class UserAction {

    @GetMapping(value = "me")
    public Principal me(Principal principal) {
        System.out.println("アクセスユーザー情報:" + principal);
        return principal;
    }
}

到此为止,SSO认证服务器的实现已经完成。请参考以下层级,包括包等等。

image.png

制作客户端应用程序

从这里开始,我们将开始制作客户端应用程序。本文将创建两个客户端应用程序,分别为client-a和client-b。

客户A的应用程序
A配置 客户A

在亲的sso-oauth2-demo项目下创建客户端a模块。
将下面的内容放入客户端a模块的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>sso-oauth2-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <artifactId>client-a</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>client-a</name>
    <description>Demo project for Spring Boot</description>

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

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

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

        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

(2)application.xml的配置
根据以下内容,将client-a配置进去。
在这里将client-a的端口定义为8301。

server:
  port: 8301
  servlet:
    session:
      cookie:
        name: CLIENT_A_SESSION

security:
  oauth2:
    client:
      client-id: SampleClientId
      client-secret: secret
      access-token-uri: http://localhost:8300/auth/oauth/token
      user-authorization-uri: http://localhost:8300/auth/oauth/authorize
    resource:
      user-info-uri: http://localhost:8300/auth/user/me # SSO認証サーバーのアドレス

spring:
  thymeleaf:
    cache: false
客户端A的实施。

实现security类
请确保在类的上方加上@EnableOAuth2Sso注解,因为它非常重要,不要忘记。

@EnableOAuth2Sso
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**")
            .authorizeRequests()
            .antMatchers("/", "/login**")
            .permitAll()
            .anyRequest()
            .authenticated();
    }

}

在resources文件夹下创建一个templates文件夹,并创建一个用于显示画面的index.html文件。
将以下内容写入index.html文件中。

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Spring Security SSO</title>
<link rel="stylesheet"
    href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
</head>

<body>
<div class="container">
    <div class="col-sm-12">
        <h1>Spring Security SSO クライアントアプリケーションA</h1>
        <a class="btn btn-primary" href="securedPage">Login</a>
    </div>
</div>
</body>
</html>

请在与上述相同的位置创建securedPage.html,并按照以下方式进行实施。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Spring Security SSO</title>
<link rel="stylesheet"
    href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
</head>

<body>
<div class="container">
    <div class="col-sm-12">
        <h1>Secured Page, Client A</h1>
        Welcome, <span th:text="${#authentication.name}">Name</span>
    </div>
</div>
</body>
</html>

(2).实现Controller类

@Controller
public class IndexAction {

    @GetMapping(value = "")
    public String index() {
        System.out.println("ClientAのindex画面へ遷移する");
        return "index.html";
    }

    @GetMapping(value = "securedPage")
    public String home() {
        System.out.println("ClientAのsecuredPage画面へ遷移する");
        return "securedPage.html";
    }
}

当我们达到这个地步,客户A已经结束了。

客户端B应用程序

由于client-b应用程序的构建方式与client-a相同,所以这里省略说明。
然而,在创建client-b应用程序时,有一个地方需要修改client-b的名称。
例如,client-b的端口被改为8302。

在此,SS0功能的实施已经全部完成。

SS0功能验证

首先,每个人都会启动三个项目。

    • auth-server

 

    • client-a

 

    client-b

然后,我们将访问客户端A。URL为http://localhost:8301。

image.png
image.png
image.png

由于通过client-a成功登录,所以在使用SSO功能访问client-b时,应该能够在client-b的主页上点击登录按钮,并且无需输入用户名和密码就能够成功登录。

好吧,我们来验证一下。

image.png

当您点击登录按钮时,您将直接进入client-b的securedPage页面,而不是转到登录页面。

image.png

所以,单一登录(SSO)功能非常顺利。

最終結論

感谢您阅读到最后。
您可以在这里下载源代码。

源代码:https://github.com/Hyman1993/Demo-Projects/tree/master/sso-oauth2-demo

bannerAds