项目结构:
1.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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cxb</groupId>
<artifactId>oauth2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-oauth2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.application.properties
#management.security.enabled=true
#management.security.role=ADMIN
#spring.security.document.name=xiaomi
#spring.security.document.password=123
logging.level.org.springframework.security=debug
server.port=8080
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=1
# 连接超时时间(毫秒)
spring.redis.timeout=10000
#是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
redis.testOnBorrow=true
#在空闲时检查有效性, 默认false
redis.testWhileIdle=true
3.AuthorizationServerConfig
package com.cxb.oauth2.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.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static final String RESOURCE_ID = "oauth-resource";
private static final String CLIENT_ID = "client_id_1";
private static final String CLIENT_SECRET = new BCryptPasswordEncoder().encode("123456");
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService detailsService;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(CLIENT_ID)
.secret(CLIENT_SECRET)
.resourceIds(RESOURCE_ID)
.authorizedGrantTypes("password", "authorization_code", "implicit", "client_credentials", "refresh_token")
.scopes("read","write")
.accessTokenValiditySeconds(3600) // token失效时间
.refreshTokenValiditySeconds(864000) //refresh token失效时间
.redirectUris("http://example.com")
.autoApprove("read");
}
/**
* 认证服务端点配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.userDetailsService(detailsService)
.tokenStore(memoryTokenStore())
.authenticationManager(authenticationManager)
//接收GET和POST
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/**
* token存储
* @return
*/
@Bean
public TokenStore memoryTokenStore() {
// 这是token存储的InMemory方式
// return new InMemoryTokenStore();
//使用redis存储token
RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
//设置redis token存储中的前缀
redisTokenStore.setPrefix("auth-token:");
return redisTokenStore;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer
// 开启/oauth/token_key验证端口无权限访问
.tokenKeyAccess("permitAll()")
// 开启/oauth/check_token验证端口认证权限访问
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
oauthServer.addTokenEndpointAuthenticationFilter(new CorsFilter(corsConfigurationSource()));
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "HEAD", "DELETE", "OPTION"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.addExposedHeader("Authorization");
configuration.addExposedHeader("Content-disposition");//文件下载消息头
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
4.ResourceServerConfig
package com.cxb.oauth2.config;
import lombok.extern.slf4j.Slf4j;
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;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
@Slf4j
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "oauth-resource";
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(true);
}
// http://localhost:8080/oauth/authorize?response_type=code&client_id=client_id_1&redirect_uri=http://example.com
@Override
public void configure(HttpSecurity http) throws Exception {
http.logout().deleteCookies("JSESSIONID");
http.csrf().disable();
http.formLogin().disable();
http.sessionManagement().disable();
http.cors();
http.requestMatchers().antMatchers("/user/**")
.and()
.authorizeRequests().anyRequest().authenticated();
}
}
5.WebSecurityConfig
package com.cxb.oauth2.config;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
@SneakyThrows
public AuthenticationManager authenticationManagerBean() {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@SneakyThrows
public void configure(AuthenticationManagerBuilder auth) {
auth.userDetailsService(userDetailsService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
http.formLogin().permitAll();
http.logout().permitAll().deleteCookies("JSESSIONID");
http.csrf().disable();
http.authorizeRequests()
.antMatchers( "/login").permitAll()
.anyRequest().authenticated();
}
@Override
@Bean
@SneakyThrows
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
userDetailsService.createUser(User.withUsername("admin")
.password(passwordEncoder().encode("123456"))
.authorities("ROLE_ADMIN").build());
userDetailsService.createUser(User.withUsername("xiaoming")
.password(passwordEncoder().encode("123456"))
.authorities("ROLE_USER").build());
return userDetailsService;
}
}
6.DeptController
package com.cxb.oauth2.web;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yutao
* @create 2019-10-24 14:35
**/
@RestController
@RequestMapping("dept")
public class DeptController {
@GetMapping("get")
@PreAuthorize("hasRole('ADMIN')")
public Object get() {
return "manage dept";
}
@GetMapping("get1")
@PreAuthorize("hasRole('USER')")
public Object get1() {
return "it dept";
}
}
7.UserController
package com.cxb.oauth2.web;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yutao
* @create 2019-10-24 14:35
**/
@RestController
@RequestMapping("user")
public class UserController {
@GetMapping("get")
@PreAuthorize("hasRole('ADMIN')")
public Object get() {
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
@GetMapping("get1")
@PreAuthorize("hasRole('USER')")
public Object get1() {
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
8.SpringbootOauth2Application
package com.cxb.oauth2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class SpringbootOauth2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootOauth2Application.class, args);
}
}
9.测试:
### 密码模式
```$xslt
localhost:8080/oauth/token?client_id=client_id_1&client_secret=123456&grant_type=password&username=admin&password=123456
{
"access_token": "1e8ada36-ed82-49fc-9ca7-ff897d4bf2b2",
"token_type": "bearer",
"refresh_token": "9ba4580d-3497-4634-9696-af51a8b9bd41",
"expires_in": 4,
"scope": "all"
}
```
### 简化模式
```$xslt
1、如下地址输入,会重定向 http://localhost:8080/login
http://localhost:8080/oauth/authorize?response_type=token&client_id=client_id_1&redirect_uri=http://example.com&scope=write
2、http://localhost:8080/login 输入账号密码,然后再次输入
http://localhost:8080/oauth/authorize?response_type=token&client_id=client_id_1&redirect_uri=http://example.com&scope=write
3、获取token
http://example.com/#access_token=3194ab36-e019-4557-8007-7a7fb2482542&token_type=bearer&expires_in=99http://example.com/#access_token=3194ab36-e019-4557-8007-7a7fb2482542&token_type=bearer&expires_in=99
```
### 授权码模式
```$xslt
1、如下地址输入,会重定向 http://localhost:8080/login
http://localhost:8080/oauth/authorize?response_type=code&client_id=client_id_1&redirect_uri=http://example.com&scope=write
2、http://localhost:8080/login 输入账号密码,然后再次输入
http://localhost:8080/oauth/authorize?response_type=code&client_id=client_id_1&redirect_uri=http://example.com&scope=write
3、http://example.com/?code=n9WlYp 获取授权码
4、http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=client_id_1&client_secret=123456&redirect_uri=http://example.com&code=n9WlYp
{
"access_token": "812403de-548d-4939-b0fa-57b1ec8abb11",
"token_type": "bearer",
"refresh_token": "8a8a7ebb-6f73-452d-9582-2fbf4ff80815",
"expires_in": 99,
"scope": "write"
}
```
### 客户端模式
```
localhost:8080/oauth/token?client_id=client_id_1&client_secret=123456&grant_type=client_credentials
{
"access_token": "c8ae1362-2084-4dbc-986c-847b7fe66f6c",
"token_type": "bearer",
"expires_in": 4,
"scope": "all"
}
```
### refresh token
```$xslt
$ curl -X GET "localhost:8080/oauth/token?client_id=client_id_1&client_secret=123456&grant_type=refresh_token&refresh_token=fca82077-720e-46da-9c88-c2f221b0cb46"
{"access_token":"0274a51f-f61b-4bb0-9ae4-b99ef508c91e","token_type":"bearer","refresh_token":"fca82077-720e-46da-9c88-c2f221b0cb46","expires_in":99,"scope":"read write"}
```
### 使用token获取资源
```$xslt
$ curl -H "Authorization:bearer 284a5718-0a80-4eab-9d04-1bda3b6ceb62" -X GET http://localhost:8080/user/get
{"error":"invalid_token","error_description":"Invalid access token: 284a5718-0a80-4eab-9d04-1bda3b6ceb62"}
或者
$ curl -X GET http://localhost:8080/user/get?access_token=0f22ff90-1834-4654-9576-8737ec84d61e
```
刷新token: