SpringSeSpringSecurity+OAuth2实现单点登录系统(详细教程+源码)

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

文章目录

    • 一、父级项目sso-oauth2-demo

    • 二、授权服务器auth-server

    • 三、客户端应用client-a与client-b

    • 四、启动与测试



本节源码已上传到Git:

https://github.com/laolunsi/spring-boot-examples/tree/master/04-sso-oauth2-demo上,请放心食用

本节利用Spring Security Oauth2实现SpringBoot项目的单点登录功能。

创建三个SpringBoot应用:auth-server, client-a, client-b,其中auth-server是授权服务器,用于登录、获取用户信息。

本节采用SpringBoot 2.1.9.RELEASE和Spring security 2.1.9.RELEASE


一、父级项目sso-oauth2-demo

首先我们创建一个父maven项目,取名sso-oauth2-demo。而上面说的三个应用是这个项目的子应用。父级项目maven引入如下配置:

<?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>

<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>

这个父级项目只有一个maven的pom.xml,不需要代码。下面我们开始创建授权服务器和客户端应用A/B


二、授权服务器auth-server

在这个sso-oauth2-demo项目下创建子SpringBoot项目——auth-server,引入如下依赖配置:

<?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>

这里我们可以看到是继承了父级的sso-oauth2-demo项目,从父级项目继承了一些公共的依赖。

配置:

server:
port: 8300
servlet:
context-path: '/auth'

PS:授权服务器的配置文件比较简单,不需要其他东西。

启动类:

@SpringBootApplication
@EnableResourceServer
public class AuthServerApplication {

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

}

PS:启用资源服务器

下面需要进行一些关于auth和security的配置,这里需要两个类:

/**
* 授权服务器配置
*/

@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");
}

// 必须进行redirectUris的配置,否则请求授权码时会报错:error="invalid_request", error_description="At least one redirect_uri must be registered with the client."
}

PS:BCryptPasswordEncoder如果注入不了,可以直接尝试new。

/**
* security基本配置
* 本demo中,登录用户名与密码是固定的,实际项目中应该从数据库读取
*/

@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("eknown")
.password(passwordEncoder().encode("123"))
.roles("USER");
}

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

到这一步,授权服务器的基本配置已经完成了,下面我们需要提供一个获取登陆用户信息的接口:

/**
* 该接口类中的唯一接口,用于ClientA和ClientB在登录成功后获取用户信息用
* 该接口地址可以任意修改,只要与ClientA/B中配置的用户信息地址一致即可
*/

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

@GetMapping(value = "me")
public Principal me(Principal principal) {
System.out.println("调用me接口获取用户信息:" + principal);
return principal;
}
}

PS:上述接口将交由ClientA和ClientB使用,在配置中指明,用于登录后获取当前的用户信息。

好了,下面我们创建两个测试应用——client-a和client-b

三、客户端应用client-a与client-b

创建父级maven的子SpringBoot项目——client-a和client-b,这里仅展示client-a的创建和配置过程,而client-b仅是名字不同而已。

修改maven文件:

<?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>

<!-- 引入thymeleaf和thymeleaf security的依赖 -->
<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>

配置,这里较为复杂,务必注意:

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 # 从授权服务器获取当前登录用户信息的地址

spring:
thymeleaf:
cache: false

PS:上面的user-info-uri与之前的auth-server中的接口对应上了!

启动类不需要修改。下面需要一个security的配置类:

@EnableOAuth2Sso
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

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

}

到这一步,基本配置已经完成了。下面我们来创建测试页面和接口:

在resources文件夹下——即application.yml的同级目录下,创建一个templaes文件夹, 并放入两个页面:

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>

PS:index.html是不需要验证的,而securedPage或使用authentication.name,所以需要授权。

下面我们创建获取页面的接口:

/**
* 获取页面的接口
*/

@Controller
public class IndexAction {

@GetMapping(value = "")
public String index() {
System.out.println("进入ClientA首页");
return "index.html";
}

@GetMapping(value = "securedPage")
public String home() {
System.out.println("进入ClientA securedPage");
return "securedPage.html";
}
}

然后按照上面的方式,同样步骤创建client-b项目,注意修改一些client-a和client-b相关的名称或配置。

创建完毕后,我们就可以进入测试阶段了!


四、启动与测试

启动这三个项目,分别运行在8300/8301/8302三个端口上。

首先我们访问http://localhost:8301,进入到index.html:

SpringSeSpringSecurity+OAuth2实现单点登录系统(详细教程+源码)

点击login,注意这个接口仅仅是打开了securedPage.html,而由于securedPage.html使用了需要进行授权的用户信息,会oauth2自动重定向到auth-server对应的http://localhost:8300/auth/login页面了:

SpringSeSpringSecurity+OAuth2实现单点登录系统(详细教程+源码)

输入默认的用户名eknow和密码123进行登录:

SpringSeSpringSecurity+OAuth2实现单点登录系统(详细教程+源码)

下面打开client-b的首页:

SpringSeSpringSecurity+OAuth2实现单点登录系统(详细教程+源码)

点击login后,发现并没有重定向到auth-server的登录页面,而是获取到了用户数据,进入了clientB的secured页面,这表示单点登录成功了!

也就是ClientA登录成功后,位于同一浏览器上的ClientB应用,自动进行了授权验证操作,不需要再次登录了!

SpringSeSpringSecurity+OAuth2实现单点登录系统(详细教程+源码)

好了,到这一步,我们的sso-oauth2-demo项目已经完成了!


参考:

  1. Simple Single Sign-On With Spring Security OAuth2: https://www.baeldung.com/sso-spring-security-oauth2


原文始发于微信公众号(猿生物语):SpringSeSpringSecurity+OAuth2实现单点登录系统(详细教程+源码)