# 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模式) ![1693987867877](pic/1693987867877.jpg) ![1693988168494](pic/1693988168494.jpg) ## 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 resp = restTemplate.exchange(url, HttpMethod.POST,httpEntity, Map.class); Map body= resp.getBody(); System.out.println(resp.getBody()); AuthToken token = new AuthToken(body.get("access_token").toString(),body.get("refresh_token").toString() ,body.get("jti").toString(),Integer.parseInt(body.get("expires_in").toString()+""),body.get("scope").toString()); return token; } } ``` ``` package com.dfsk.client.domain; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data @Builder @AllArgsConstructor @NoArgsConstructor public class AuthToken { private String accessToken; private String refreshToken; private String jti; private Integer expiresIn; private String scope; } ``` ![1693988210698](pic/1693988210698.jpg)