Spring Security Oauth2
授權伺服器
-
Authorize Endpoint
:授權端點,進行授權 -
Token Endpoint
:令牌端點,經過授權拿到對應的Token -
Introspection Endpoint
:校驗端點,校驗Token的合法性 -
Revocation Endpoint
:撤銷端點,撤銷授權
Spring Security Oauth2架構
流程:
-
使用者訪問,此時沒有Token。Oauth2RestTemplate會報錯,這個報錯資訊會被Oauth2ClientContextFilter捕獲並重定向到認證伺服器
-
認證伺服器通過Authorization Endpoint進行授權,並通過AuthorizationServerTokenServices生成授權碼並返回給客戶端
-
客戶端拿到授權碼去認證伺服器通過Token Endpoint呼叫AuthorizationServerTokenServices生成Token並返回給客戶端
-
客戶端拿到Token去資源伺服器訪問資源,一般會通過Oauth2AuthenticationManager呼叫ResourceServerTokenServices進行校驗。校驗通過可以獲取資源。
Spring Security Oauth2授權碼模式
建立專案
2、新增依賴
<?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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yjxxt</groupId>
<artifactId>springsecurityoauth2demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springsecurityoauth2demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3、編寫實體類
User.java
package com.yjxxt.springsecurityoauth2demo.pojo;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
/**
* 使用者類
*/
public class User implements UserDetails {
private String username;
private String password;
private List<GrantedAuthority> authorities;
public User(String username, String password, List<GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
4、編寫Service
UserService.java
package com.yjxxt.springsecurityoauth2demo.service;
import com.yjxxt.springsecurityoauth2demo.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/**
* @author ylc
* @since 1.0.0
*/
@Service
public class UserService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password = passwordEncoder.encode("123456");
return new User("admin",password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
5、編寫Controller
UserController.java
package com.yjxxt.springsecurityoauth2demo.controller;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author ylc
* @since 1.0.0
*/
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication) {
return authentication.getPrincipal();
}
}
6、編寫配置類
SecurityConfig.java
package com.yjxxt.springsecurityoauth2demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Spring Security 配置類
*
* @author ylc
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/oauth/**", "/login/**", "/logout/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
}
AuthorizationServerConfig.java
package com.yjxxt.springsecurityoauth2demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
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;
/**
* 授權伺服器配置
* @author ylc
* @since 1.0.0
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client_id
.withClient("admin")
//配置client-secret
.secret(passwordEncoder.encode("112233"))
//配置redirect_uri,用於授權成功後跳轉
.redirectUris("http://www.baidu.com")
//配置申請的許可權範圍
.scopes("all")
//配置grant_type,表示授權型別
.authorizedGrantTypes("authorization_code");
}
}
ResourceServerConfig.java
package com.yjxxt.springsecurityoauth2demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
/**
* 資源伺服器配置
*
* @author ylc
* @since 1.0.0
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.requestMatchers()
.antMatchers("/user/**");//配置需要保護的資源路徑
}
}
7、測試
獲取授權碼
附帶上客戶端id、回撥url、獲取請求的型別
輸入賬戶密碼
點選授權獲取授權碼
根據授權碼獲取令牌(POST請求)
-
grant_type
:授權型別,填寫authorization_code,表示授權碼模式 -
code
:授權碼,就是剛剛獲取的授權碼,注意:授權碼只使用一次就無效了,需要重新申請。 -
client_id
:客戶端標識 -
redirect_uri
:申請授權碼時的跳轉url,一定和申請授權碼時用的redirect_uri一致。 -
scope
:授權範圍。
認證失敗服務端返回 401 Unauthorized
注意:此時無法請求到令牌,訪問伺服器會報錯
根據token去資源伺服器拿資源
如果修改token就會報錯
Spring Security Oauth2 密碼模式
在上面的程式碼中進行適當的修改即可
SecurityConfig.java
package com.yjxxt.springsecurityoauth2demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Spring Security 配置類
*
* @author ylc
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/oauth/**", "/login/**", "/logout/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
}
AuthorizationServerConfig.java
package com.yjxxt.springsecurityoauth2demo.config;
import com.yjxxt.springsecurityoauth2demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
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.AuthorizationServerEndpointsConfigurer;
/**
* 授權伺服器配置
* @author ylc
* @since 1.0.0
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
/**
* 使用密碼模式需要配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client_id
.withClient("admin")
//配置client-secret
.secret(passwordEncoder.encode("112233"))
//配置訪問token的有效期
.accessTokenValiditySeconds(3600)
//配置重新整理token的有效期
.refreshTokenValiditySeconds(864000)
//配置redirect_uri,用於授權成功後跳轉
.redirectUris("http://www.baidu.com")
//配置申請的許可權範圍
.scopes("all")
//配置grant_type,表示授權型別
.authorizedGrantTypes("authorization_code","password");
}
}
測試:
在Redis中儲存token
之前的程式碼我們將token直接存在記憶體中,這在生產環境中是不合理的,下面我們將其改造成儲存在Redis中
新增依賴及配置
pom.xml
<!--redis 依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 物件池依賴 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
application.properties
# Redis配置
spring.redis.host=192.168.10.100
編寫Redis配置類
RedisConfig.java
package com.yjxxt.springsecurityoauth2demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
/**
* 使用redis儲存token的配置
* @author ylc
* @since 1.0.0
*/
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore redisTokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
}
在認證伺服器配置中指定令牌的儲存策略為Redis
package com.yjxxt.springsecurityoauth2demo.config;
import com.yjxxt.springsecurityoauth2demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
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.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* 授權伺服器配置
* @author ylc
* @since 1.0.0
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Autowired
@Qualifier("redisTokenStore")
private TokenStore tokenStore;
/**
* 使用密碼模式需要配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
.tokenStore(tokenStore);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client_id
.withClient("admin")
//配置client-secret
.secret(passwordEncoder.encode("112233"))
//配置訪問token的有效期
.accessTokenValiditySeconds(3600)
//配置重新整理token的有效期
.refreshTokenValiditySeconds(864000)
//配置redirect_uri,用於授權成功後跳轉
.redirectUris("http://www.baidu.com")
//配置申請的許可權範圍
.scopes("all")
//配置grant_type,表示授權型別
.authorizedGrantTypes("password");
}
}
測試:
使用密碼模式請求token