附:源代码 Github 地址:Spring Security + OAuth2.0 + JWT 实现单点登录
配置文件
bootstrap.yml
server:
port: 8081
spring:
main:
allow-bean-definition-overriding: true
pom.xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.security.oauth.bootgroupId>
<artifactId>spring-security-oauth2-autoconfigureartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.security.oauth.bootgroupId>
<artifactId>spring-security-oauth2-autoconfigureartifactId>
<version>2.1.11.RELEASEversion>
dependency>
dependencies>
package com.natsumes.ame.config;
import com.natsumes.ame.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
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.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;
/**
* 该配置类,主要处理用户名和密码的校验等事宜
*/
@EnableWebSecurity(debug = true)
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 配置拦截资源
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.requestMatchers().antMatchers("/**")
.and().authorizeRequests()
.antMatchers("/oauth/**").permitAll() // /oauth/**接口对外开放
.anyRequest().authenticated() // 在创建过滤器的基础上的一些自定义配置
.and().formLogin()
.and().httpBasic(); //Basic认证
}
/**
* 处理用户名和密码验证事宜
* 1)客户端传递username和password参数到认证服务器
* 2)一般来说,username和password会存储在数据库中的用户表中
* 3)根据用户表中数据,验证当前传递过来的用户信息的合法性
* 可以通过自定义用户校验类MyUserDetailsService来自定义用户信息
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder);
}
/**
* 密码解析器,这里使用 BCryptPasswordEncoder 加密
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
package com.natsumes.ame.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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;
import java.util.ArrayList;
@Slf4j
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
/**
* Locates the user based on the username. In the actual implementation, the search
* may possibly be case sensitive, or case insensitive depending on how the
* implementation instance is configured. In this case, the UserDetails
* object that comes back may have a username that is of a different case than what
* was actually requested..
*
* 可以根据自身业务实现认证
*
* @param username the username identifying the user whose data is required.
* @return a fully populated user record (never null
)
* @throws UsernameNotFoundException if the user could not be found or the user has no
* GrantedAuthority
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("username={}", username);
if (!username.equals("admin")) {
throw new UsernameNotFoundException("the username is not found");
}
//用户角色在数据库中获取
String role = "ROLE_ADMIN";
ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(role));
//线上环境应该通过用户名查询数据库获取加密后的密码
String password = passwordEncoder.encode("123456");
return new User(username, password, authorities);
}
}
package com.natsumes.ame.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.jwt.crypto.sign.MacSigner;
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.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private PasswordEncoder passwordEncoder;
/**
*
* 认证服务器最终是以api接⼝的⽅式对外提供服务(校验合法性并⽣成令牌、校验令牌等)
* 那么,以api接⼝⽅式对外的话,就涉及到接⼝的访问权限,我们需要在这⾥进⾏必要的配置
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
security.allowFormAuthenticationForClients() // 允许客户端表单认证
.tokenKeyAccess("permitAll()") // 开启端口/oauth/token_key的访问权限(允许)
.checkTokenAccess("permitAll()"); // 开启端口/oauth/check_token的访问权限(允许)
}
/**
* 客户端详情配置
* ⽐如client_id,secret
* 当前这个服务就如同QQ平台,本网站作为客户端需要qq平台进⾏登录授权认证等,提前需到QQ平台注册,QQ平台会给我们
* 颁发client_id等必要参数,表明客户端是谁
* @param clients client 配置
* @throws Exception 异常信息
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//可通过数据库配置
clients.inMemory()
.withClient("order-client")
.resourceIds("order-server", "user-server", "oauth-server")
.secret(passwordEncoder.encode("order-secret-8888"))
.authorizedGrantTypes("refresh_token", "authorization_code", "password", "implicit", "client_credentials")
.accessTokenValiditySeconds(3600)
.scopes("all")
.autoApprove(true) //自动确认授权
//自动认证并跳转获取token
.redirectUris("http://127.0.0.1:8081/oauth/token?grant_type=authorization_code&client_id=order-client&client_secret=order-secret-8888")
.and()
.withClient("user-client")
.resourceIds("order-server", "user-server", "oauth-server")
.secret(passwordEncoder.encode("user-secret-8888"))
.authorizedGrantTypes("refresh_token", "authorization_code", "password", "implicit", "client_credentials")
.accessTokenValiditySeconds(3600)
.scopes("all")
.autoApprove(true)
.redirectUris("http://www.baidu.com")
.and().build();
//clients.withClientDetails();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// jwt token 方式
endpoints.authenticationManager(authenticationManager)
.tokenServices(authorizationServerTokenServices())
//.userDetailsService(myUserDetailsService)
.tokenStore(tokenStore())
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("test123");
converter.setVerifier(new MacSigner("test123"));
return converter;
}
/**
* 该方法用户获取一个token服务对象(该对象描述了token有效期等信息)
*/
private AuthorizationServerTokenServices authorizationServerTokenServices() {
// 使用默认实现
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setSupportRefreshToken(true); // 是否开启令牌刷新
defaultTokenServices.setTokenStore(tokenStore());
// 针对jwt令牌的添加
defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
// 设置令牌有效时间(一般设置为2个小时)
defaultTokenServices.setAccessTokenValiditySeconds(2 * 60 * 60); // access_token就是我们请求资源需要携带的令牌
// 设置刷新令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200); // 3天
return defaultTokenServices;
}
}
http://localhost:8081/oauth/authorize?response_type=code&client_id=order-client&scope=all
首先获取授权code,然后通过code获取token
http://localhost:8081/oauth/authorize?response_type=code&client_id=user-client&scope=all&redirect_uri=http://www.baidu.com
浏览器跳转到 https://www.baidu.com/?code=DctdGQ
使用 code 获取 token
http://localhost:8081/oauth/token?grant_type=authorization_code&code=DctdGQ&username=admin&password=123456&client_id=user-client&client_secret=user-secret-8888&redirect_uri=http://www.baidu.com
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjEwNDQsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiYjlhZTJjMzMtNjBhOS00ZTAwLThlNjYtNzU2M2RjODg1MmU3IiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.BXoQBuDFbx7x81ShSZaRM5FvaFWAYWEXEUgPlP2-UMk",
"token_type":"bearer",
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTU1NzMwNDQsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiNmVjZDk5MmEtOGI1Zi00NjA2LTk1NmItMmNhYjk4MzI0Yjg2IiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiYjlhZTJjMzMtNjBhOS00ZTAwLThlNjYtNzU2M2RjODg1MmU3In0.U6LNCmjZNqlxLc3MzNanNaJrJo7PbAwMK-as07QPih0",
"expires_in":7199,
"scope":"all",
"jti":"b9ae2c33-60a9-4e00-8e66-7563dc8852e7"
}
http://localhost:8081/oauth/token?grant_type=password&username=admin&password=123456&client_id=order-client&client_secret=order-secret-8888
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjEyMjgsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiZjhmNjkwNzItNjk2My00MGE5LWE5YWUtMWE5YzQ1MzI1OTA2IiwiY2xpZW50X2lkIjoib3JkZXItY2xpZW50Iiwic2NvcGUiOlsiYWxsIl19.nMvNO76FZl0y0mCUZNv9TBvw7dxJqTk69xGAQ45ZTZc",
"token_type":"bearer",
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTU1NzMyMjgsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiNjk1Y2E4M2QtZjVjMS00MjM4LThiMDYtOTkwMDk4MTJkZGU4IiwiY2xpZW50X2lkIjoib3JkZXItY2xpZW50Iiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImY4ZjY5MDcyLTY5NjMtNDBhOS1hOWFlLTFhOWM0NTMyNTkwNiJ9.owg9G_N1CWvToQWcxnY-Aq-pLXtaLNbM6qhrsqpAaiE",
"expires_in":7199,
"scope":"all",
"jti":"f8f69072-6963-40a9-a9ae-1a9c45325906"
}
http://localhost:8081/oauth/authorize?response_type=token&client_id=user-client&client_secret=user-secret-8888&scope=all&redirect_uri=http://www.baidu.com
浏览器返回
https://www.baidu.com/#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjEzNDgsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiMWZkNDc2MzgtODI2OC00ZWU2LTkxYmItYzdlMDQ3MTQ0MmIwIiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.ujUPZOWgWwhuXYmWtmV0l4d9308S1gzeJodZLQglJBA&token_type=bearer&expires_in=7199&jti=1fd47638-8268-4ee6-91bb-c7e0471442b0
http://localhost:8081/oauth/token?client_id=user-client&client_secret=user-secret-8888&grant_type=client_credentials
{ "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNTk1MzIxNDMyLCJqdGkiOiJjNTljMGM5NS0yNDgwLTQ4NWQtOWZhYS0zMjE5OTllNDJmYTYiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.dHMRaw4jXwGGEsQJxokAHd-CLKYjT71ZW-17Baw9THw",
"token_type":"bearer",
"expires_in":7199,
"scope":"all",
"jti":"c59c0c95-2480-485d-9faa-321999e42fa6"
}
package com.natsumes.ame.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class Controller {
@GetMapping("/auto/test")
public String test1() {
return "auto test success: " + new Date();
}
@GetMapping("/demo/test")
public String test2() {
return "demo test success: " + new Date();
}
@GetMapping("test3")
public String test3() {
return "test3 success: " + new Date();
}
}
不需要认证:
http://localhost:8081/test3
test3 success: Tue Jul 21 15:44:53 CST 2020
需要认证:
不带 token 访问
http://localhost:8081/auto/test
{
"timestamp": "2020-07-21T07:32:20.383+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/auto/test"
}
http://localhost:8081/demo/test
{
"timestamp": "2020-07-21T07:34:24.209+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/demo/test"
}
带 token 访问
http://localhost:8081/demo/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjQ4MjQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJjNzM3NmUyMi1lOTRkLTRjZjktYjUyZS03Zjg5YzQ5MmVjYzIiLCJjbGllbnRfaWQiOiJvcmRlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.S_EnUvucm9wy-stS4jcKz8MZUJOOzYv_PGk4iSfDmYU
demo test success: Tue Jul 21 15:47:21 CST 2020
检验 token
http://localhost:8081/oauth/check_token?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjQ4MjQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJjNzM3NmUyMi1lOTRkLTRjZjktYjUyZS03Zjg5YzQ5MmVjYzIiLCJjbGllbnRfaWQiOiJvcmRlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.S_EnUvucm9wy-stS4jcKz8MZUJOOzYv_PGk4iSfDmYU
{
"user_name": "admin",
"scope": [
"all"
],
"active": true,
"exp": 1595324824,
"authorities": [
"ROLE_ADMIN"
],
"jti": "c7376e22-e94d-4cf9-b52e-7f89c492ecc2",
"client_id": "order-client"
}
刷新 token
http://localhost:8081/oauth/token?grant_type=refresh_token&client_id=user-client&client_secret=user-secret-8888&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTU1NzMwNDQsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiNmVjZDk5MmEtOGI1Zi00NjA2LTk1NmItMmNhYjk4MzI0Yjg2IiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiYjlhZTJjMzMtNjBhOS00ZTAwLThlNjYtNzU2M2RjODg1MmU3In0.U6LNCmjZNqlxLc3MzNanNaJrJo7PbAwMK-as07QPih0
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjYzNTksInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiNzk2ZWY0M2QtMzUxOS00N2QxLTg0MTYtNzQ3MThhMTJjMjNjIiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.SPR1_o0nVegPgziVrIwLy4NILq-NWHClm97RnBfNUY0",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTU1NzMwNDQsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiNmVjZDk5MmEtOGI1Zi00NjA2LTk1NmItMmNhYjk4MzI0Yjg2IiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiNzk2ZWY0M2QtMzUxOS00N2QxLTg0MTYtNzQ3MThhMTJjMjNjIn0.mxnB-BUqfdqEfZMaoC75gPKRLNGBUMO1f_E7GelE9rA",
"expires_in": 7199,
"scope": "all",
"jti": "796ef43d-3519-47d1-8416-74718a12c23c"
}
将认证服务改造为资源服务器
package com.natsumes.ame.config;
import org.springframework.beans.factory.annotation.Autowired;
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.http.SessionCreationPolicy;
import org.springframework.security.jwt.crypto.sign.MacSigner;
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;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@EnableResourceServer
@Configuration
@EnableWebSecurity
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
resources.resourceId("oauth-server").tokenStore(tokenStore).stateless(true);
}
/**
* 场景:一个服务中可能有很多资源(API接口)
* 某一些API接口,需要先认证,才能访问
* 某一些API接口,压根就不需要认证,本来就是对外开放的接口
* 我们就需要对不同特点的接口区分对待(在当前configure方法中完成),设置是否需要经过认证
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http // 设置session的创建策略(根据需要创建即可)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/auto/**").authenticated() // auto为前缀的请求需要认证
.antMatchers("/demo/**").authenticated() // demo为前缀的请求需要认证
.anyRequest().permitAll();
}
}
不需要认证:
http://localhost:8081/test3
test3 success: Tue Jul 21 17:07:15 CST 2020
需要认证:
http://localhost:8081/demo/test
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
带token访问:
http://localhost:8081/demo/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjQ4MjQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJjNzM3NmUyMi1lOTRkLTRjZjktYjUyZS03Zjg5YzQ5MmVjYzIiLCJjbGllbnRfaWQiOiJvcmRlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.S_EnUvucm9wy-stS4jcKz8MZUJOOzYv_PGk4iSfDmYU
demo test success: Tue Jul 21 17:09:35 CST 2020
上面已经搭建好认证服务,接下来再创建两个服务 user-server 和 order-server 作为资源服务器
以 order-server 为例
package com.natsumes.ame.config;
import org.springframework.beans.factory.annotation.Autowired;
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.http.SessionCreationPolicy;
import org.springframework.security.jwt.crypto.sign.MacSigner;
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;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@EnableResourceServer
@Configuration
@EnableWebSecurity
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
resources.resourceId("order-server").tokenStore(tokenStore).stateless(true);
}
/**
* 场景:一个服务中可能有很多资源(API接口)
* 某一些API接口,需要先认证,才能访问
* 某一些API接口,压根就不需要认证,本来就是对外开放的接口
* 我们就需要对不同特点的接口区分对待(在当前configure方法中完成),设置是否需要经过认证
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http // 设置session的创建策略(根据需要创建即可)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/order/need-oauth/**").authenticated() // auto为前缀的请求需要认证
.anyRequest().permitAll();
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("test123");
converter.setVerifier(new MacSigner("test123"));
return converter;
}
}
package com.natsumes.ame.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/no-oauth/test")
public String test1() {
return new Date() + ": order test1 success";
}
@GetMapping("/need-oauth/test")
public String test2() {
return new Date() + ": order test2 success";
}
}
不需要认证:
http://localhost:8083/order/no-oauth/test
Tue Jul 21 17:19:25 CST 2020: order test1 success
需要认证:
http://localhost:8083/order/need-oauth/test
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
带token访问:
http://localhost:8083/order/need-oauth/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjQ4MjQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJjNzM3NmUyMi1lOTRkLTRjZjktYjUyZS03Zjg5YzQ5MmVjYzIiLCJjbGllbnRfaWQiOiJvcmRlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.S_EnUvucm9wy-stS4jcKz8MZUJOOzYv_PGk4iSfDmYU
Tue Jul 21 17:21:33 CST 2020: order test2 success