Spring Security Oauth2
OAuth是一个关于授权的开放网络标准,在全世界得到的广泛的应用,目前是2.0的版本。OAuth2在“客户端”与“服务提供商”之间,设置了一个授权层(authorization layer)。“客户端”不能直接登录“服务提供商”,只能登录授权层,以此将用户与客户端分离。“客户端”登录需要获取OAuth提供的令牌,否则将提示认证失败而导致客户端无法访问服务。关于OAuth2这里就不多作介绍了,网上资料详尽。下面我们实现一个 整合 SpringBoot 、Spring Security OAuth2 来实现单点登录功能的案例并对执行流程进行详细的剖析。
案例实现
项目介绍
这个单点登录系统包括下面几个模块:
spring-oauth-parent : 父模块,管理打包
spring-oauth-server : 认证服务端、资源服务端(端口:8881)
spring-oauth-client : 单点登录客户端示例(端口:8882)
spring-oauth-client2: 单点登录客户端示例(端口:8883)
当通过任意客户端访问资源服务器受保护的接口时,会跳转到认证服务器的统一登录界面,要求登录,登录之后,在登录有效时间内任意客户端都无需再登录。
认证服务端
添加依赖
主要是添加 spring-security-oauth2 依赖。
pom.xml
复制代码
4.0.0
<artifactId>spring-oauth-server</artifactId>
<name>spring-oauth-server</name>
<packaging>war</packaging>
<parent>
<groupId>com.louis</groupId>
<artifactId>spring-oauth-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<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>
<version>${oauth.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
</dependencies>
复制代码 配置文件
配置文件内容如下。
application.yml
server:
port: 8881
servlet:
context-path: /auth
启动类
启动类添加 @EnableResourceServer 注解,表示作为资源服务器。
OAuthServerApplication.java
复制代码
package com.louis.spring.oauth.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
@SpringBootApplication
@EnableResourceServer
public class OAuthServerApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(OAuthServerApplication.class, args);
}
}
复制代码
认证服务配置
添加认证服务器配置,这里采用内存方式获取,其他方式获取在这里定制即可。
OAuthServerConfig.java
复制代码
package com.louis.spring.oauth.server.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
@Configuration
@EnableAuthorizationServer
public class OAuthServerConfig 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") // clientId, 可以类比为用户名
.secret(passwordEncoder.encode("secret")) // secret, 可以类比为密码
.authorizedGrantTypes("authorization_code") // 授权类型,这里选择授权码
.scopes("user_info") // 授权范围
.autoApprove(true) // 自动认证
.redirectUris("http://localhost:8882/login","http://localhost:8883/login") // 认证成功重定向URL
.accessTokenValiditySeconds(10); // 超时时间,10s
}
}
复制代码
安全配置
Spring Security 安全配置。在安全配置类里我们配置了:
-
配置请求URL的访问策略。
-
自定义了同一认证登录页面URL。
-
配置用户名密码信息从内存中创建并获取。
SecurityConfig.java
复制代码
package com.louis.spring.oauth.server.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login")
.antMatchers("/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll() // 自定义登录页面,这里配置了 loginPage, 就会通过 LoginController 的 login 接口加载登录页面
.and().csrf().disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 配置用户名密码,这里采用内存方式,生产环境需要从数据库获取
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123"))
.roles("USER");
}
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
复制代码
接口提供
这里提供了一个自定义的登录接口,用于跳转到自定义的同一认证登录页面。
LoginController.java
复制代码
package com.louis.spring.oauth.server.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
/**
* 自定义登录页面
* @return
*/
@GetMapping("/login")
public String login() {
return "login";
}
}
复制代码
登录页面放置在 resources/templates 下,需要在登录时提交 pos t表单到 auth/login。
login.ftl
复制代码
统一认证登录平台
UserController.java
复制代码
package com.louis.spring.oauth.server.controller;
import java.security.Principal;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
/**
* 资源服务器提供的受保护接口
* @param principal
* @return
*/
@RequestMapping("/user")
public Principal user(Principal principal) {
System.out.println(principal);
return principal;
}
}
复制代码
客户端实现
添加依赖
主要添加 Spring Security 依赖,另外因为 Spring Boot 2.0 之后代码的合并, 需要添加 spring-security-oauth2-autoconfigure ,才能使用 @EnableOAuth2Sso 注解。
pom.xml
复制代码
4.0.0
<artifactId>spring-oauth-client</artifactId>
<name>spring-oauth-client</name>
<packaging>war</packaging>
<parent>
<groupId>com.louis</groupId>
<artifactId>spring-oauth-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>${oauth-auto.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
</dependencies>
复制代码 启动类
启动类需要添加 RequestContextListener,用于监听HTTP请求事件。
OAuthClientApplication.java
复制代码
package com.louis.spring.oauth.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.request.RequestContextListener;
@SpringBootApplication
public class OAuthClientApplication extends SpringBootServletInitializer {
@Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
public static void main(String[] args) {
SpringApplication.run(OAuthClientApplication.class, args);
}
}
复制代码
安全