# spring-security-oauth2-jwt
**Repository Path**: yahebo/spring-security-oauth2-jwt
## Basic Information
- **Project Name**: spring-security-oauth2-jwt
- **Description**: spring security整合oauth2 jwt,并实现令牌私钥加密,公钥验签
- **Primary Language**: Java
- **License**: AGPL-3.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 2
- **Created**: 2024-01-22
- **Last Updated**: 2024-01-22
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# spring security整合oauth2以及jwt总结(密码模式)
pom.xml添加相关依赖
```pom
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.4.RELEASE
com.dfsk
auth
0.0.1-SNAPSHOT
auth
auth
1.8
8
8
Greenwich.SR2
8.0.19
2.3.0.RELEASE
org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.cloud
spring-cloud-starter-security
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
com.alibaba
fastjson
1.2.79
com.alibaba
druid
1.2.18
org.springframework.boot
spring-boot-starter-jdbc
mysql
mysql-connector-java
org.projectlombok
lombok
com.baomidou
mybatis-plus-boot-starter
3.5.3
io.jsonwebtoken
jjwt-api
0.11.2
io.jsonwebtoken
jjwt-impl
0.11.2
runtime
io.jsonwebtoken
jjwt-jackson
0.11.2
runtime
org.bouncycastle
bcprov-jdk15on
1.70
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
```
数据库表
*** 其中密码为BCryptPasswordEncoder.encode("123456a")加密,可参考8、重要的方法 ***
## 1 、继承WebSecurityConfigurerAdapter
继承该类并重写protected void configure(HttpSecurity http) throws Exception方法,您可以定制应用程序的安全性需求,并配置认证、授权和其他安全相关的功能
```java
package com.dfskauth.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;
@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().permitAll()//表单提交放行
.and()
.csrf().disable();//csrf关闭
}
//注册解码器
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//注册AuthenticationManager
@Bean
public AuthenticationManager getAuthenticationManager() throws Exception {
return super.authenticationManager();
}
}
```
## 2 、若采用jdbc获取clientid和password以及授权方式,则需要配置datasource
相关代码如下
```java
package com.dfskauth.config;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
@ConfigurationProperties(prefix = "spring.datasource")
@Data
public class DataSourceConfig {
private String url;
private String username;
private String password;
@Bean(name = "oauth2DataSource")
public DataSource getOauth2DataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setUsername(username);// 用户名
dataSource.setPassword(password);// 密码
return dataSource;
}
}
```
完整的配置文件如下
```yaml
server:
port: 8090
spring:
datasource:
url: jdbc:mysql://49.234.11.126:3306/test?characterEncoding=utf8&allowMultiQueries=true&useSSL=false&serverTimezone=UTC
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
redis:
database: 0
host: 49.234.11.126
port: 6379
password:
timeout: 1000
pool:
max-active: 200
max-wait: -1
max-idle: 10
min-idle: 0
mybatis-plus:
type-aliases-package: com.dfskauth.domain
mapper-locations: classpath:mapperxml/*.xml
encrypt:
key-store:
alias: dfskkey
location: dfskkey.jks
secret: dfsk@123
password: dfsk@123
token:
#令牌有效时间
expire-second: 5400
#刷新令牌有效时间
refresh-second: 7200
```
## 3、实现 `TokenEnhancer` 接口
作用是在生成访问令牌(Access Token)时对令牌进行自定义处理,以增强令牌的功能或添加额外的信息。通过实现 `TokenEnhancer` 接口,您可以在访问令牌的生成过程中修改或扩展令牌的属性
```java
package com.dfskauth.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.oauth2.common.*;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Component
public class JwtTokenEnhancer implements TokenEnhancer {
@Value("${token.expire-second}")
private Long expireSecond;
@Value("${token.refresh-second}")
private Long refreshSecond;
//令牌功能增强
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken defaultAccessToken = (DefaultOAuth2AccessToken) accessToken;
Map info = new HashMap<>();
info.put("enhance","enhance info");
defaultAccessToken.setAdditionalInformation(info);
defaultAccessToken.setExpiration(Date.from(Instant.now().plusSeconds(expireSecond)));
defaultAccessToken.setRefreshToken(createRefreshToken(authentication));
return defaultAccessToken;
}
//为了动态设置刷新令牌时间
private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
DefaultExpiringOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken(UUID.randomUUID().toString(),
Date.from(Instant.now().plusSeconds(refreshSecond)));
return refreshToken;
}
}
```
## 4、设置令牌存储策略
### 4.1 设置 tokenStore
`TokenStore` 是 Spring Security 中用于管理和存储 OAuth2 访问令牌(Access Token)和刷新令牌(Refresh Token)的接口。它定义了令牌的存储和获取方式,提供了与令牌相关的操作。
### 4.2 设置 JwtAccessTokenConverter
`JwtAccessTokenConverter` 是 Spring Security 中的一个关键组件,用于将 OAuth2 访问令牌(Access Token)转换为 JSON Web Token (JWT) 格式,以及将 JWT 格式的令牌转换回 OAuth2 访问令牌。它在 OAuth2 和 JWT 之间提供了一个桥梁,提供了令牌的编码、解码和验证功能。
```java
package com.dfskauth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
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 org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import java.security.KeyPair;
@Configuration
public class JwtTokenStoreConfig {
//JwtAccessTokenConverter
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
//加载证书
ClassPathResource resource = new ClassPathResource("dfskkey.jks");
//读取证书数据
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource,"dfsk@123".toCharArray());
//获取私钥,
KeyPair keyPair = keyStoreKeyFactory.getKeyPair("dfskkey","dfsk@123".toCharArray());
jwtAccessTokenConverter.setKeyPair(keyPair);
return jwtAccessTokenConverter;
}
@Bean("jwtTokenStore")
public TokenStore jwtTokenStore(){
JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter());
return jwtTokenStore;
}
}
```
其中生成公钥私钥的方法如下
新建文件夹,cmd执行命令
```bash
keytool -genkeypair -alias dfskkey -keyalg RSA -keypass dfsk@123 -keystrore dfskkey.jks -storepass dfsk@123
```
## 5、授权配置,继承AuthorizationServerConfigurerAdapter
完整代码如下
```java
package com.dfskauth.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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 javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
UserDetailsService userDetailsServiceImpl;
@Autowired
AuthenticationManager authenticationManager;
@Autowired
public RedisConnectionFactory redisConnectionFactory;
@Autowired
@Qualifier("jwtTokenStore")
TokenStore jwtTokenStore;
@Autowired
JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
@Qualifier("oauth2DataSource")
private DataSource dataSource;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
/**
* 配置授权端点
* 密码模式必须配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//配置Jwt内容增强器
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List delegates = new ArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsServiceImpl)
//jwt存储令牌策略
.tokenStore(jwtTokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(enhancerChain);
}
/**
* 配置客户端
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
}
```
### 5.1 配置授权端点,重写public void configure(AuthorizationServerEndpointsConfigurer endpoints)方法
### 5.2 配置客户端,重写public void configure(ClientDetailsServiceConfigurer clients)方法
## 6、实现UserDetailsService接口
实现 `UserDetailsService` 接口的作用是为 Spring Security 提供获取用户详细信息的能力。当需要对用户进行身份验证和授权时,Spring Security 在内部会使用 `UserDetailsService` 来获取用户信息。
`UserDetailsService` 接口中定义了一个方法 `loadUserByUsername()`,该方法接收一个用户名参数,并返回一个实现了 `UserDetails` 接口的对象,该对象包含了用户的详细信息,例如用户名、密码、角色和权限等。
通过实现 `UserDetailsService`,您可以根据您的业务需求,将用户信息从任何数据源(如数据库、LDAP、Web服务等)中检索出来。您可以自定义逻辑来获取用户信息,然后将其封装到 `UserDetails` 对象中并返回。这样,Spring Security 将能够使用这些用户信息进行身份验证和授权决策。
`UserDetailsService` 的实现通常与其他 Spring Security 组件(如 `AuthenticationManager`、`UserDetailsAuthenticationProvider` 等)一起使用,用于验证用户的凭据,生成认证对象,并进行后续的授权操作。
需要注意的是,您还需要在实现 `UserDetailsService` 接口的同时,根据返回的用户信息实现 `UserDetails` 接口来描述用户的详细信息。`UserDetails` 接口包括了一些必要的方法,如获取用户名、密码、角色、权限等,用于供 Spring Security 进行认证和授权的操作。
总结来说,实现 `UserDetailsService` 接口的作用是提供获取用户详细信息的能力,用于用户的身份验证和授权过程中,为 Spring Security 提供访问用户信息的途径。
```java
package com.dfskauth.service;
import com.dfskauth.domain.CustomUserDetails;
import com.dfskauth.domain.DfskSysRole;
import com.dfskauth.domain.DfskSysUser;
import com.dfskauth.mapper.DfskSysRoleMapper;
import com.dfskauth.mapper.DfskSysUserMapper;
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;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private DfskSysUserMapper sysUserMapper;
@Autowired
private DfskSysRoleMapper sysRoleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DfskSysUser sysUser = sysUserMapper.getByUserName(username);
if(sysUser==null) {
throw new UsernameNotFoundException("用户名不存在!");
}
CustomUserDetails user = new CustomUserDetails(username,sysUser.getPassword(), AuthorityUtils.
commaSeparatedStringToAuthorityList(getRoleStr(username)));
return user;
}
/**
* 获取权限
* @param userName
* @return
*/
public String getRoleStr(String userName){
List roles = sysRoleMapper.getRolesByUserName(userName);
List roleKeys = roles.stream().map(DfskSysRole::getRoleKey).collect(Collectors.toList());
StringBuilder sb = new StringBuilder();
roleKeys.forEach(e->{
sb.append("ROLE_"+e+",");
});
return sb.toString().substring(0,sb.length()-1);
}
}
```
## 7、相关接口
### 7.1 获取令牌(password模式)


## 8、重要的方法
```java
package com.dfskauth;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import org.bouncycastle.util.io.pem.PemReader;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.jwt.crypto.sign.Signer;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class CreateJwtTest {
@Test
public void testCreateToken(){
//加载证书
ClassPathResource resource = new ClassPathResource("dfskkey.jks");
//读取证书数据
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource,"dfsk@123".toCharArray());
//获取私钥,
KeyPair keyPair = keyStoreKeyFactory.getKeyPair("dfskkey","dfsk@123".toCharArray());
RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate();
//创建令牌,私钥加盐(RSA算法)
Map payload = new HashMap<>();
payload.put("name","david");
payload.put("address","重庆");
Jwt jwt = JwtHelper.encode(JSON.toJSONString(payload), new RsaSigner(privateKey));
String token = jwt.getEncoded();
System.out.println(token);
}
@Test
public void parseToken(){
String token="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJkYXZpZCIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2OTM5MzUzNTAsImF1dGhvcml0aWVzIjpbIlJPTEVfZGZzayJdLCJqdGkiOiI4ODFmZmIzMC0zZjRjLTQ5MzctOGRhNC0yZTY3MjBjNmY1MmUiLCJjbGllbnRfaWQiOiJjbGllbnQiLCJlbmhhbmNlIjoiZW5oYW5jZSBpbmZvIn0.SuNVoYtNrGIgZuPnXblOD-zjkgdBC6sCKAzXR8Cnzt89alwuTguGYtKdRZXj4spaqtLZF__xDtJPoBWFmduJw3NHBpwnd-tfBVGCk8iyhCYpCkeepkH7Gxsu-4F0Awqdd1iE6lGW-h2FQQ-ebefi0flmH5vp8wrglOqvFIEwu1zw0mA9PiQBCCyCuAalQ42-Vvcdd-6IP2QQTzOCF0p7rSaWPIs_BYLzG6ZNFP4LY3ukFW8oh2O-fOJQ3QCBqy3hvG7xp733j2peFYqUWEPh3Slg-fIK_VYzTc2LAMJ-U5f-JkeUr9SjDnJd7tzQ7l8SjFWNNjCao_C_5XTX_REcKA";
String publicKey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAitciIE/prJFLA60Y2kLXuBLHyDEhQ1gfvaK+kJjTKSXZMeeUy1+VKnXwjRrLtQjby0kEBatY6Qa8Rdm2WCdKyJO++Vz0nwwwC3B59VzClEwTSqNa5rl0s4hcIWhRmS2BAdn+A5A/ukXXX075O8t0P6Q6Nymu5qprQaM6JE2Vw9e9erdclhywuS1BA/GyybrjxhYaqIqRIO5DhY3mayp1oP2CVa8PudyFO1N4YZUZ54mhc2tvrqKz1d7wY+/P7J2Pa8SJkRb1mgC7wIPsXSABMXxiS8nU0aOt50/BuUIuq9p4hn5TUBGEmP4EynNsZ6EF07LmKa0avpyqshR6xgMBvQIDAQAB-----END PUBLIC KEY-----";
Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publicKey));
String claims = jwt.getClaims();
System.out.println(claims);
}
/**
* 可以运行的
* @throws Exception
*/
@Test
public void testTokenParse() throws Exception{
String publicKeyStr = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAitciIE/prJFLA60Y2kLXuBLHyDEhQ1gfvaK+kJjTKSXZMeeUy1+VKnXwjRrLtQjby0kEBatY6Qa8Rdm2WCdKyJO++Vz0nwwwC3B59VzClEwTSqNa5rl0s4hcIWhRmS2BAdn+A5A/ukXXX075O8t0P6Q6Nymu5qprQaM6JE2Vw9e9erdclhywuS1BA/GyybrjxhYaqIqRIO5DhY3mayp1oP2CVa8PudyFO1N4YZUZ54mhc2tvrqKz1d7wY+/P7J2Pa8SJkRb1mgC7wIPsXSABMXxiS8nU0aOt50/BuUIuq9p4hn5TUBGEmP4EynNsZ6EF07LmKa0avpyqshR6xgMBvQIDAQAB-----END PUBLIC KEY-----";
String token="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTM5ODczODYsInVzZXJfbmFtZSI6ImRhdmlkIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9kZnNrIl0sImp0aSI6IjU4ZTI5ODc2LTdlZGItNDNjNS1hMDUzLTJhNzQ0YzZkYjE0NyIsImNsaWVudF9pZCI6ImNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.fA1yJbkBcRfFhh5BHrwnSaSCumO2jSbjC4W4lOTyyV2ccdaEcd2tMGFDvSG_PiJwaHmSBxykIbi5xy4OIf_t7CbDXf_UTDmqRa4OBqz25fO5yH7v1UuQsnJ_c_MaxqvHen9MvfPIWL1ZkqjB35fhMWz2xGuOKWjRIOmiFFYuwKd79LANH6l0FKgo-9AGOW00nkviYAOSIbs-HH5VTDYZ4bBlhxo6Xv1nfUBiynj9C9Jy_kMyI9StjdKtwSf0YHMmqSvxG_OeA6yIT5vI8kJYQ3xwiOum--nxTgm1143Si70nruo0wvnO15QgGi1juwXSNargx7ps84jYXRPz4lfR1Q";
String publicKeyPath = "e://public.pem";
byte[] publicKeyBytes = Files.readAllBytes(Paths.get(publicKeyPath));
String publicKeyString = new String(publicKeyBytes);
// 解析公钥 PEM 格式
String publicKeyPEM = publicKeyString
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] publicKeyBytesDecoded = Base64.getDecoder().decode(publicKeyPEM);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytesDecoded);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);
// 解析和验证 JWT 令牌
Jws claimsJws = Jwts.parserBuilder()
.setSigningKey(publicKey)
.build()
.parseClaimsJws(token);
Claims claims = claimsJws.getBody();
System.out.println(claims);
}
/**
*
*/
@Test
public void testBase64Authorization() throws UnsupportedEncodingException {
String authorizationStr = "Y2xpZW50OjEyMzQ1Ng==";
byte[] codes = Base64.getDecoder().decode(authorizationStr);
String str = new String(codes,"utf-8");
System.out.println(str);
}
}
```
*** 获取clientId密码,可直接修改oauth_client_details表中的client_secret数据 ***
```
package com.dfskauth;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
@SpringBootTest
class AuthApplicationTests {
@Resource
private PasswordEncoder passwordEncoder;
@Test
void contextLoads() {
String pwd = passwordEncoder.encode("123456a");
System.out.println("密码:"+pwd);
}
}
```
# 资源服务器搭建
maven相关配置如下
```xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.4.RELEASE
com.dfsk
auth-client
0.0.1-SNAPSHOT
auth-client
auth-client
1.8
8
8
Greenwich.SR2
org.springframework.boot
spring-boot-starter-oauth2-resource-server
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-oauth2
io.jsonwebtoken
jjwt
0.9.1
io.jsonwebtoken
jjwt
0.9.1
cn.hutool
hutool-all
5.8.21
com.alibaba.fastjson2
fastjson2
2.0.40
org.projectlombok
lombok
1.18.28
provided
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
```
application.yml配置文件如下
```yaml
server:
port: 8091
jwt:
public-key: -----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAitciIE/prJFLA60Y2kLXuBLHyDEhQ1gfvaK+kJjTKSXZMeeUy1+VKnXwjRrLtQjby0kEBatY6Qa8Rdm2WCdKyJO++Vz0nwwwC3B59VzClEwTSqNa5rl0s4hcIWhRmS2BAdn+A5A/ukXXX075O8t0P6Q6Nymu5qprQaM6JE2Vw9e9erdclhywuS1BA/GyybrjxhYaqIqRIO5DhY3mayp1oP2CVa8PudyFO1N4YZUZ54mhc2tvrqKz1d7wY+/P7J2Pa8SJkRb1mgC7wIPsXSABMXxiS8nU0aOt50/BuUIuq9p4hn5TUBGEmP4EynNsZ6EF07LmKa0avpyqshR6xgMBvQIDAQAB-----END PUBLIC KEY-----
securityinfo:
client-id: client
client-secret: 123456
```
## 1、继承ResourceServerConfigurerAdapter
资源服务器用于保护和控制访问受保护资源的请求。
通过继承 `ResourceServerConfigurerAdapter` 类,并覆盖其中的方法,您可以实现以下功能和作用:
1. 配置资源服务器的访问规则:通过重写 `configure(HttpSecurity http)` 方法,您可以定义资源服务器上的访问规则和安全约束。您可以指定哪些路径、资源需要进行身份验证,以及是否需要具有特定的角色或权限才能访问。
2. 配置令牌解析器和验证器:通过重写 `configure(ResourceServerSecurityConfigurer resources)` 方法,您可以设置令牌解析器和验证器,用于验证和解析传入请求中的访问令牌。您可以选择使用默认的令牌解析器和验证器,或者使用自定义的实现来满足业务需求。
3. 配置资源服务器的资源ID:通过重写 `configure(ResourceServerSecurityConfigurer resources)` 方法,您可以设置资源服务器的资源标识符(Resource ID),以便客户端在请求访问令牌时进行匹配。资源ID通常与授权服务器配置的客户端信息中的资源ID进行匹配,用于验证请求中的令牌是否授权了对该资源的访问。
4. 配置资源服务器的异常处理器:通过重写 `configure(HttpSecurity http)` 方法,您可以自定义处理资源服务器上的异常情况。您可以为资源服务器设置异常处理器,以定义当请求没有访问权限时应该返回的错误信息或进行其他操作。
通过继承 `ResourceServerConfigurerAdapter` 并根据您的需求重写相关方法,您可以定制和配置资源服务器的行为和属性。资源服务器用于保护和控制对受保护资源的访问,因此适当的配置对于实现安全、可靠的资源访问控制至关重要。
```java
package com.dfsk.client.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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;
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 org.springframework.web.client.RestTemplate;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//注入application.properties文件中的键值
@Value("${jwt.key}")
private String jwtKey;
@Value("${jwt.public-key}")
private String publicKey;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.POST,"/user/login").permitAll() // 允许 /user/login 路径的访问
.anyRequest().authenticated() // 其他路径需要进行身份验证
.and()
.formLogin(); // 使用表单登录
}
//配置 TokenStore
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenStore(tokenStore());
}
@Bean
public TokenStore tokenStore() {
//声明TokenStore并将其添加到Spring上下文中。
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
//创建一个访问令牌转换器并设置用于验证令牌签名的对称密钥。
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(publicKey);
return converter;
}
/**
* http调用时用到
* @return
*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
```
## 2、如业务需要,可改造登录获取令牌方式
模拟请求认证中心的请求获取令牌接口http://localhost:8090/oauth/token
代码如下
```java
package com.dfsk.client.controller;
import com.dfsk.client.domain.AuthToken;
import com.dfsk.client.domain.UserLoginParam;
import com.dfsk.client.service.ILoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class LoginController {
@Autowired
private ILoginService loginService;
@PostMapping("/login")
@ResponseBody
public AuthToken login(@RequestBody UserLoginParam param){
return loginService.login(param.getUsername(),param.getPassword());
}
}
```
```java
package com.dfsk.client.service;
import com.dfsk.client.domain.AuthToken;
public interface ILoginService {
public AuthToken login(String username, String password);
}
```
```java
package com.dfsk.client.service.impl;
import com.dfsk.client.domain.AuthToken;
import com.dfsk.client.service.ILoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
@Service
public class LoginServiceImpl implements ILoginService {
@Value("${securityinfo.client-id}")
private String clientId;
@Value("${securityinfo.client-secret}")
private String clientSecret;
@Autowired
private RestTemplate restTemplate;
@Override
public AuthToken login(String username, String password) {
String url = "http://localhost:8090/oauth/token";
MultiValueMap parameterMap = new LinkedMultiValueMap<>();
parameterMap.add("username",username);
parameterMap.add("password",password);
parameterMap.add("grant_type","password"); ;
String authorization = "Basic "+ Base64.getEncoder().encodeToString((clientId+":"+clientSecret).getBytes(StandardCharsets.UTF_8));
MultiValueMap headerMap = new LinkedMultiValueMap<>();
headerMap.add("Authorization",authorization);
HttpEntity httpEntity = new HttpEntity(parameterMap,headerMap);
ResponseEntity