概念 | 定义 |
---|---|
认证 | 判断一个用户身份是否合法的过程(登录过程) |
会话 | 为了避免用户的每次操作都进行认证, 将用户的信息保存在会话中. 常见的会话有基于session 的模式和基于token 的模式 |
授权 | 校验用户是否有权限访问某个资源 认证是为了验证用户的身份; 授权是为了验证用户是否有权限访问某个资源 |
SessionId
返回给用户SessionId
; 服务端据此判断客服端是否认证过Token
的模式Token
返回给用户Token
, 服务端校验该Token判断客户端是否认证过Session
模式, 服务端无需存储Token
例如JWT令牌Session
的认证方式注: 基于Session
的认证机制由Servlet
规范定制, Servlet
容器已实现
pom.xml-->
<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.0modelVersion>
<packaging>jarpackaging>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.12.RELEASEversion>
<relativePath/>
parent>
<groupId>com.passnightgroupId>
<artifactId>spring-noteartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>spring-notename>
<description>spring-notedescription>
<properties>
<java.version>11java.version>
<spring-cloud.version>Hoxton.SR12spring-cloud.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
project>
Controller
package com.passnight.springboot.security.controller;
import com.passnight.springboot.security.service.AuthenticationService;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/login")
@NoArgsConstructor
public class LoginController {
AuthenticationService authenticationService;
@GetMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
return String.valueOf(authenticationService.login(username, password));
}
@GetMapping("/version")
public String version() {
return "version";
}
@Autowired
public LoginController(AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
}
Service
package com.passnight.springboot.security.service;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class AuthenticationService {
public boolean login(@NonNull String username, @NonNull String password) {
return username.equals("user") && password.equals("123456");
}
}
Test
package com.passnight.springboot.security.web;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import java.util.Map;
@SpringBootTest
public class LoginWebTest {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://localhost:8080");
RestTemplate restTemplate = new RestTemplate();
@Test
public void version() {
String result = restTemplate.getForObject("/login/version", String.class);
Assertions.assertEquals("version", result);
}
@Test
public void failedToLogin() {
Map<String, String> param = Map.of("username", "user", "password", "wrong password");
String result = restTemplate.getForObject(uriBuilderFactory
.uriString("/login/login")
.queryParam("username", "user")
.queryParam("password", "wrong password")
.build(),
String.class);
Assertions.assertEquals("false", result);
}
@Test
public void login() {
String result = restTemplate.getForObject(uriBuilderFactory
.uriString("/login/login")
.queryParam("username", "user")
.queryParam("password", "123456")
.build(),
String.class);
Assertions.assertEquals("true", result);
}
}
Session
@GetMapping("/login-with-session")
public String loginWithSession(@RequestParam String username, @RequestParam String password, HttpSession session) {
if (authenticationService.login(username, password)) {
session.setAttribute(PROTECTED_RESOURCE_TOKEN, true);
return "login success";
} else {
return "failed to login";
}
}
@GetMapping("/resource-protected-by-session")
public String resourceProtectedBySession(HttpSession session) {
if (session.getAttribute(PROTECTED_RESOURCE_TOKEN) != null) {
return "resource protected by session";
} else {
return "you are not allowed to request this resource";
}
}
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate();
return "logout success";
}
@Test
public void requestResourceProtectedBySession() {
String result = restTemplate.getForObject(uriBuilderFactory
.uriString("/login/resource-protected-by-session")
.build(),
String.class);
System.out.println(result);
}
// you are not allowed to request this resource
# 使用浏览器访问; RestTemplate不会共享Session; 如果想共享资源可以在cookie中添加jssion
http://localhost:8080/login/login-with-session?username=user&password=123456
# login success
http://localhost:8080/login/resource-protected-by-session
# resource protected by session
http://localhost:8080/login/logout
# logout success
http://localhost:8080/login/resource-protected-by-session?username=user
# you are not allowed to request this resource
SpringSecurity
实现
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
package com.passnight.springboot.security.config;
import org.springframework.context.annotation.Bean;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 定义用户信息
@Override
@Bean
protected UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// 创建两个默认用户
manager.createUser(User.withUsername("user1").password("123456").authorities("r3").build());
manager.createUser(User.withUsername("user2").password("654321").authorities("r4").build());
return manager;
}
// 密码编辑器
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
// 安全拦截器
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 限制资源的访问
.antMatchers("/resource/**")
.authenticated()
.anyRequest()
.permitAll()
.and()
.formLogin()
.successForwardUrl("/index");
}
}
package com.passnight.springboot.security.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/resource")
public class ResourceController {
@GetMapping("/r1")
public String r1() {
return "r1";
}
@GetMapping("/r2")
public String r2() {
return "r2";
}
@GetMapping("/r3")
public String r3() {
return "r3";
}
@GetMapping("/r4")
public String r4() {
return "r4";
}
}
@Override
@Bean
protected UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// 创建两个默认用户; user1只有r3授权, user2只有r4授权
manager.createUser(User.withUsername("user1").password("123456").authorities("r3").build());
manager.createUser(User.withUsername("user2").password("654321").authorities("r4").build());
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 为r3添加鉴权
.antMatchers("/resource/r3").hasAnyAuthority("r3")
// 为r4添加鉴权
.antMatchers("/resource/r4").hasAnyAuthority("r4")
// 其他的只需要登录即可
.antMatchers("/resource/**")
.authenticated()
.anyRequest()
.permitAll()
.and()
.formLogin()
.successForwardUrl("/index");
}
user1
访问r1
结果user1
访问r3
结果user1
访问r4
结果@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) // 开启基于注解的实现
@Security // 指定安全性, 如资源权限或角色等; 不支持EL表达式
@PreAuthority // 在方法前执行鉴权
@PostAuthority // 方法后执行鉴权
// 只有拥有"p1"权限才能访问
@GetMapping("r5")
@PreAuthorize("hasAuthority('p1')")
public String r5() {
return "r5";
}
// 只有同时拥有"p1", p2"权限才能访问
@GetMapping("r6")
@PostAuthorize("hasAuthority('p1') and hasAuthority('p2')")
public String r6() {
return "r6";
}
SpringSecurity
结构SpringSecurity
主要解决的问题是安全访问控制SpringSecurity
对Web资源的限制主要是通过Filter实现的需求:
Session
有以下做法OAuth是一个
OAuth2主要分为两个服务, 授权服务和资源服务:
/oauth/authorize
/oauth/token
clientId
和密码用于认证客户Token
, 都可以访问资源![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=figures%2FS%5D(https%3A%2F%2Fimg-blog.csdnimg.cn%2F3d77716669494f33b7c1343278f52776.png&pos_id=img-F9sXKyte-1695448537072)
+--------+ +---------------+
| |--(A)------- Authorization Grant --------->| |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
package com.passnight.cloud.security.authserver.config;
import org.springframework.context.annotation.Configuration;
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;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client") // 客户端Id
.secret("654321") // 秘钥
.redirectUris("https://www.baidu.com") // 重定向地址
.scopes("all") // 授权范围
.authorizedGrantTypes("authorization_code"); // 授权类型(授权码)
}
}
package com.passnight.cloud.security.authserver.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;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.requestMatchers()
.antMatchers("/user/**")
.and()
.csrf().disable();
}
}
package com.passnight.cloud.security.authserver.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;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/**", "/login/**", "logout/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.and()
.csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
package com.passnight.cloud.security.authserver.service;
import com.passnight.cloud.security.authserver.config.SecurityConfig;
import lombok.AllArgsConstructor;
import org.springframework.security.core.authority.AuthorityUtils;
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;
import java.util.List;
@Service
@AllArgsConstructor
public class UserService implements UserDetailsService {
private final List<UserDetails> users = new ArrayList<>();
{
PasswordEncoder passwordEncoder = new SecurityConfig().passwordEncoder();
users.add(User.builder().username("user1").password(passwordEncoder.encode("123456")).authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("admin")).build());
users.add(User.builder().username("user2").password(passwordEncoder.encode("123456")).authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("user")).build());
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return users.stream()
.filter(user -> user.getUsername().equals(username))
.findAny()
.orElseThrow(() -> new UsernameNotFoundException(String.format("Can't find user: %s", username)));
}
}
package com.passnight.cloud.security.authserver.controller;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("getCurrentUser")
public Object getCurrentUser(Authentication authentication) {
return authentication.getPrincipal();
}
}
http://server.passnight.local:8101/oauth/authorize?client_id=client&response_type=code
询问你是否授权客户端权限访问受保护的资源
并拿到授权码
# 请求, 并获得token
passnight@passnight-s600:~$ curl --location 'http://server.passnight.local:8101/oauth/token' --header 'Content-Type: application/x-www-form-urlencoded' --header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' --header 'Cookie: JSESSIONID=DE362C8D16D0FA00EB979D1D8D4D9A7B' --data-urlencode 'grant_type=authorization_code' --data-urlencode 'client_id=client' --data-urlencode 'redirect_uri=https://www.baidu.com' --data-urlencode 'code=iT2Zzx' --data-urlencode 'scope=all'
{"access_token":"ae37e184-ec54-422f-ad40-17d0e420f03a","token_type":"bearer","expires_in":43155,"scope":"all"}
@Bean
@SneakyThrows
public AuthenticationManager authenticationManager() {
return super.authenticationManager();
}
// 添加密码模式配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService);
}
// 显示声明
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client")
.secret(passwordEncoder.encode("654321"))
.redirectUris("https://www.baidu.com")
.scopes("all")
.authorizedGrantTypes("authorization_code", "password"); // 添加密码模式
}
passnight@passnight-s600:~$ curl --location 'http://server.passnight.local:8101/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' \ # username=client & password=654321 (请求去掉注释)
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=all' \
--data-urlencode 'username=user1' \
--data-urlencode 'password=123456'
{"access_token":"3a354527-d25e-4940-9dc0-4ccedbe9f0a8","token_type":"bearer","expires_in":43199,"scope":"all"}
每次请求的时候都要携带用户名和密码
Cookie
对象Session
对象Cookie
和Session
来鉴权Cookie
也会被删除Token
给客户端Token
, 请求携带该Token
, 服务端通过验证Token
鉴权JSON Web Tokens - jwt.io
JSON
, 抑郁解析JWT
可以不依赖认证服务完成授权JWT
令牌较长, 占用存储空间较大// 头部; 可以使用base64进行编码
{
"alg": "HS265",
"typ": "JWT"
}
// 负载(payload), 即存放有效信息的地方
// 可以分类为: 标准中注册的声明, 公共的声明, 私有的声明; 私有的声明需要通知验证规则, 公有声明不需要
// 一般不添加敏感信息, 因为不加密
{
"sub": "123456", // 标准声明
"name": "User name", // 公共声明
"iat": 123456 // 私有声明
}
// 签证, 签名(signature): 由头部, 负载, 和盐(保密)组成
JJWT
JJWT
是一个提供对JWT
端到端验证的Java
库
Oauth
中 <dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
Jwt
package com.passnight.cloud.security.authserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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
public class JwtConfig {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
final JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey("secret_key");
return jwtAccessTokenConverter;
}
}
OAuth
中package com.passnight.cloud.security.authserver.config;
import com.passnight.cloud.security.authserver.service.UserService;
import lombok.AllArgsConstructor;
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;
@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private PasswordEncoder passwordEncoder;
private AuthenticationManager authenticationManager;
private UserService userService;
private TokenStore tokenStore;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
.tokenStore(tokenStore); // 设置tokenStore为JWT TokenStore
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client") // 客户端Id
.secret(passwordEncoder.encode("654321")) // 秘钥
.redirectUris("https://www.baidu.com") // 重定向地址
.scopes("all") // 授权范围
.authorizedGrantTypes("authorization_code", "password"); // 授权类型(授权码)
}
}
passnight@passnight-s600:~$ curl --location 'http://server.passnight.local:8101/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' \ # username=client & password=654321 (请求去掉注释)
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=all' \
--data-urlencode 'username=user1' \
--data-urlencode 'password=123456'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTEyNjU0NzQsInVzZXJfbmFtZSI6InVzZXIxIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiYWNlZTViYTYtMDU2My00ZjliLTgwMTEtZWVhYWQzZGI0YzZiIiwiY2xpZW50X2lkIjoiY2xpZW50Iiwic2NvcGUiOlsiYWxsIl19.avWqGdBfclj5wPkNOjSMmUBY3h0NCc1EkxAPAjhWZms","token_type":"bearer","expires_in":43199,"scope":"all","jti":"acee5ba6-0563-4f9b-8011-eeaad3db4c6b"}
Enhancer
package com.passnight.cloud.security.authserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
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;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class JwtConfig {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey("secret_key");
return jwtAccessTokenConverter;
}
// 注入Enhancer
@Bean
public TokenEnhancer jwtTokenEnhancer() {
return new JwtTokenEnhancer();
}
}
// 实现TokenEnhancer
class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> map = new HashMap<>();
map.put("enhance", "enhance info");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map);
return accessToken;
}
}
package com.passnight.cloud.security.authserver.config;
import com.passnight.cloud.security.authserver.service.UserService;
import lombok.AllArgsConstructor;
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.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private PasswordEncoder passwordEncoder;
private AuthenticationManager authenticationManager;
private UserService userService;
private TokenStore tokenStore;
private JwtAccessTokenConverter jwtAccessTokenConverter;
private TokenEnhancer jwtTokenEnhancer;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
// 设置增强内容
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> tokenEnhancerDelegates = new ArrayList<>();
tokenEnhancerDelegates.add(jwtTokenEnhancer);
tokenEnhancerDelegates.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(tokenEnhancerDelegates);
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
.tokenStore(tokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
// 添加TokenEnhancer
.tokenEnhancer(tokenEnhancerChain);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client") // 客户端Id
.secret(passwordEncoder.encode("654321")) // 秘钥
.redirectUris("https://www.baidu.com") // 重定向地址
.scopes("all") // 授权范围
.authorizedGrantTypes("authorization_code", "password"); // 授权类型(授权码)
}
}
passnight@passnight-s600:~$ curl --location 'http://server.passnight.local:8101/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=all' \
--data-urlencode 'username=user1' \
--data-urlencode 'password=123456'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2OTEyNjc3ODMsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6ImYzNWQ3ZjQwLWM2NjgtNGY5Yi1hZDhjLWExNTRmNDA5Mjc5MyIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.k6lhmr1VTvKxAPO8As9u1popZKpUiexUXTaK884XELc","token_type":"bearer","expires_in":43199,"scope":"all","enhance":"enhance info","jti":"f35d7f40-c668-4f9b-ad8c-a154f4092793"}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client")
.secret(passwordEncoder.encode("654321"))
.redirectUris("https://www.baidu.com"
.scopes("all")
.accessTokenValiditySeconds(5) // 添加失效时间
.refreshTokenValiditySeconds(86400) // 刷新令牌的失效时间
.authorizedGrantTypes("authorization_code", "password", "refresh_token"); // 添加刷新令牌
}
passnight@passnight-s600:~$ curl --location 'http://server.passnight.local:8101/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' \
--header 'Cookie: JSESSIONID=3BC47F5E2D70C4204D5CCE8F522E58F7' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=all' \
--data-urlencode 'username=user1' \
--data-urlencode 'password=123456'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2OTEyMjYwNTgsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6ImZkYjQwNjIxLTg1YzktNDU4NS1iNDQ0LTRiMDc3YTNiZGE1YSIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.ZTJStxlFuTdH8hjNyVG0OofQc6Fcv-pgQVSqVoTMQ5g","token_type":"bearer","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJmZGI0MDYyMS04NWM5LTQ1ODUtYjQ0NC00YjA3N2EzYmRhNWEiLCJleHAiOjE2OTEzMTI0NTMsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6IjRmMGUyZTZiLTY3YzUtNGYyZi1hYTZmLWMyYWEwNWI5NzI0OSIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.SHMe8Mc3YfYA94D2t3vY7bHXSpN26FKlrWNAzxxANo4","expires_in":4,"scope":"all","enhance":"enhance info","jti":"fdb40621-85c9-4585-b444-4b077a3bda5a"}
# 刷新令牌
passnight@passnight-s600:~$ curl --curl --location 'http://server.passnight.local:8101/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' \
--header 'Cookie: JSESSIONID=3BC47F5E2D70C4204D5CCE8F522E58F7' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI2MDU0ZmJlMi1mMWRiLTQ0ZmYtODE5Ni1iNThkYjM5ZmRhOWIiLCJleHAiOjE2OTEzMTI0NDMsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6ImVjODQyNjcxLTNmYmYtNDk1Yy04Njg5LWQxYjJlMmZjZGRkMiIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.2jUBXVniKeQ77KjxWy_kzqX9FtfZrd9efDod8jT6jNU'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2OTEyMjYxNjksImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6IjhiZWM5NDUyLThkMDktNDRhYy05ZmI3LWMyODcxYmZiNmIxMyIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.MOwEWrOfwif7BGYgtJqDk1OEh7_HKny-eQo5hScX97w","token_type":"bearer","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI4YmVjOTQ1Mi04ZDA5LTQ0YWMtOWZiNy1jMjg3MWJmYjZiMTMiLCJleHAiOjE2OTEzMTI0NDMsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6ImVjODQyNjcxLTNmYmYtNDk1Yy04Njg5LWQxYjJlMmZjZGRkMiIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.RMLjKFrYGhPsSSXT1TroLx3jRMSZyz7duVv4_63B590","expires_in":4,"scope":"all","enhance":"enhance info","jti":"8bec9452-8d09-44ac-9fb7-c2871bfb6b13"}
关于JSESSIONID - 简书 (jianshu.com) ↩︎
SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-开源基础软件社区-51CTO.COM ↩︎