spring-security-oauth2-authorization-server
旧依赖的移除
长久以来,使用Spring Security整合oauth2,都是使用Spring Security Oauth2这个系列的包:
<dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</artifactId><version>${spring.security.oauth2.version}</version></dependency>
然而,这个包现在已经被Spring官方移除了,现在实现相同的功能主要使用这几个Maven依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
可以看出除了Spring Security核心依赖,只是多了一个资源服务器的依赖。而之前使用的认证服务器,变成了一个新的项目,依赖如下:
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-authorization-server</artifactId><version>0.3.1</version></dependency>
这个资源服务器的依赖只到了0.3.1的版本号,还不到1.0.0,所以也算是一个新项目。不过我尝试了下,基本上已经可以用作生产环境了,只是这个包对于token持久化的支持,只支持内存储存和SQL储存,暂时并未提供之前常用的redis持久化,不知道这是不是Spring官方针对OAuth2这么多年混乱的标准作出的回应,统一使用JWT作为token确实是不需要服务端对token进行存储了。但是这里完全可以使用常规的Opaque token进行认证,需要实现RegisteredClientRepository、OAuth2AuthorizationService和OAuth2AuthorizationConsentService这几个类。
资源服务器的配置
对于资源服务器,主要作用是提供一些用户登录后需要的资源。调用接口就可以得到这些资源了。
Maven依赖
必要的Maven依赖如下:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>Code-Resources</artifactId><version>0.0.1-SNAPSHOT</version><name>Code-Resources</name><description>Code-Resources</description><properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.6.6</spring-boot.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>17</source><target>17</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.6.6</version><configuration><mainClass>com.example.code.resources.CodeResourcesApplication</mainClass></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>
安全配置类
只需要对所有的接口开启认证:
package com.example.code.resources.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.http.SessionCreationPolicy;import org.springframework.security.web.SecurityFilterChain;@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig {@Beanpublic SecurityFilterChain httpSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {httpSecurity.authorizeRequests().anyRequest().authenticated().and().cors().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().oauth2ResourceServer().jwt();retu httpSecurity.build();}}
由于前后端分离,所以将服务端的session策略设置为无状态。
配置文件
jwt模式
配置文件需要指定认证服务器的地址和认证的方式,在jwt模式下,资源服务器会请求认证服务器的/oauth2/jwks端点,拿到公钥以后对jwt进行验证。
server: port: 9600spring: datasource:useame: rootpassword: 12345678driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/oauth_demo application:name: Code-Resources security:oauth2: resourceserver:jwt: issuer-uri: http://127.0.0.1:9500 jackson:default-property-inclusion: non_null
opaquetoken模式
也可以选择opaquetoken这种常规的redis验证方式。如果选用Opaque token模式,相对应的端点就是/oauth2/introspect。
资源提供
写一个controller,要求一定的权限。对于这个权限信息,在生成的access_token,即jwt的playload部分里本身是包含的,而资源服务器在校验权限时,会通过网络请求认证服务器获取认证服务器那边的公钥:
public Resource retrieveResource(URL url) throws IOException {HttpHeaders headers = new HttpHeaders();headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, APPLICATION_JWK_SET_JSON));ResponseEntity<String> response = this.getResponse(url, headers);if (response.getStatusCodeValue() != 200) {throw new IOException(response.toString());} else {retu new Resource((String)response.getBody(), "UTF-8");}}private ResponseEntity<String> getResponse(URL url, HttpHeaders headers) throws IOException { try { RequestEntity<Void> request = new RequestEntity(headers, HttpMethod.GET, url.toURI()); retu this.restOperations.exchange(request, String.class); } catch (Exception var4) { throw new IOException(var4); }}
公钥可以鉴别jwt的playload部分有没有被篡改过。
package com.example.code.resources.controller;import com.example.code.resources.entity.ClientEntity;import com.example.code.resources.entity.TokenEntity;import com.example.code.resources.entity.UserClientEntity;import com.example.code.resources.entity.UserEntity;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.security.core.Authentication;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.oauth2.jwt.Jwt;import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;import java.util.Map;import java.util.Optional;@RestControllerpublic class UserController {JdbcTemplate jdbcTemplate;@Autowiredpublic void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}@RequestMapping("/getResources")@CrossOrigin@PreAuthorize("hasAuthority('SCOPE_message.read')")public Map<String, Object> getResources() {HashMap<String, Object> map = new HashMap<>();Authentication authentication = SecurityContextHolder.getContext().getAuthentication();String useame = authentication.getName();String userSql = "select * from oauth2_user where useame = ?";UserEntity user = jdbcTemplate.queryForObject(userSql, new BeanPropertyRowMapper<>(UserEntity.class), useame);String queryClientSql = "select * from oauth2_authorization_consent where principal_name = ?";UserClientEntity userClientEntity = jdbcTemplate.queryForObject(queryClientSql, new BeanPropertyRowMapper<>(UserClientEntity.class), useame);String clientSql = "select * from oauth2_registered_client where id = ?";Optional<UserClientEntity> userClientEntityOptional = Optional.ofNullable(userClientEntity);if (userClientEntityOptional.isPresent()) {ClientEntity clientEntity = jdbcTemplate.queryForObject(clientSql, new BeanPropertyRowMapper<>(ClientEntity.class), userClientEntityOptional.get().getRegisteredClientId());map.put("clientInfo", clientEntity);}String tokenSql = "select * from oauth2_authorization where access_token_value = ?";Jwt jwt = (Jwt) authentication.getCredentials();String token = jwt.getTokenValue();TokenEntity tokenEntity = jdbcTemplate.queryForObject(tokenSql, new BeanPropertyRowMapper<>(TokenEntity.class), token);map.put("tokenInfo", tokenEntity);map.put("userInfo", user);retu map;}}
认证服务器的配置
认证服务器主要负责access_token的生成、refresh_token的生成和通过refresh_token换取access_token的逻辑。在jwt下,access_token一旦签发就无法管理,即便使用refresh_token换取了新的access_token,那旧的access_token仍然是可用的。
Maven配置
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.6</version><relativePath/> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>code</artifactId><version>0.0.1-SNAPSHOT</version><name>code</name><description>code</description><properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding></properties><dependencies><!--spring-authorization-server依赖--><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-authorization-server</artifactId><version>0.3.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>17</source><target>17</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.6.2</version><configuration><mainClass>com.example.code.authorization.CodeAuthorizationApplication</mainClass></configuration></plugin></plugins></build></project>
主要的依赖只需要一个认证服务器,不需要Spring Security的核心包。
跨域配置
一般架构使用前后端分离,所以设置跨域:
package com.example.code.authorization.config;import org.springframework.core.Ordered;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import java.util.Arrays;import java.util.List;@Component@Order(Ordered.HIGHEST_PRECEDENCE)public class CustomerCorsFilter extends org.springframework.web.filter.CorsFilter {public CustomerCorsFilter() {super(configurationSource());}private static UrlBasedCorsConfigurationSource configurationSource() {CorsConfiguration corsConfig = new CorsConfiguration();List<String> allowedHeaders = Arrays.asList("x-auth-token", "content-type", "X-Requested-With", "XMLHttpRequest","Access-Control-Allow-Origin","Authorization","authorization");List<String> exposedHeaders = Arrays.asList("x-auth-token", "content-type", "X-Requested-With", "XMLHttpRequest","Access-Control-Allow-Origin","Authorization","authorization");List<String> allowedMethods = Arrays.asList("POST", "GET", "DELETE", "PUT", "OPTIONS");List<String> allowedOrigins = List.of("*");corsConfig.setAllowedHeaders(allowedHeaders);corsConfig.setAllowedMethods(allowedMethods);corsConfig.setAllowedOriginPattes(allowedOrigins);corsConfig.setExposedHeaders(exposedHeaders);corsConfig.setMaxAge(36000L);corsConfig.setAllowCredentials(true);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", corsConfig);retu source;}}
配置文件
不需要额外的配置。
server: port: 9500spring: application:name: Code-Authorization datasource:useame: rootpassword: 12345678driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/oauth_demo
认证服务器配置类
对于配置文件,如果不计划开启oidc协议,那使用open id环绕的代码是可以删除的。为什么使用oidc,本质只是为了实现一种规范,调取/userinfo接口去获取用户的一些登录信息,同时获取id_token来解析一些用户信息。
package com.example.code.authorization.config;import com.example.code.authorization.entity.UserEntity;import com.example.code.authorization.service.UserService;import com.nimbusds.jose.jwk.JWKSet;import com.nimbusds.jose.jwk.RSAKey;import com.nimbusds.jose.jwk.source.ImmutableJWKSet;import com.nimbusds.jose.jwk.source.JWKSource;import com.nimbusds.jose.proc.SecurityContext;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.security.config.Customizer;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OidcClientRegistrationEndpointConfigurer;import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;import org.springframework.security.config.http.SessionCreationPolicy;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.oauth2.core.*;import org.springframework.security.oauth2.core.oidc.OidcScopes;import org.springframework.security.oauth2.core.oidc.OidcUserInfo;import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;import org.springframework.security.oauth2.jwt.*;import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;import org.springframework.security.oauth2.server.authorization.config.ClientSettings;import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;import org.springframework.security.oauth2.server.authorization.config.TokenSettings;import org.springframework.security.oauth2.server.authorization.token.*;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;import org.springframework.security.web.util.matcher.RequestMatcher;import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.CorsConfigurationSource;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import org.springframework.web.filter.CorsFilter;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.interfaces.RSAPrivateKey;import java.security.interfaces.RSAPublicKey;import java.time.Duration;import java.util.*;@Configurationpublic class SecurityConfig {JdbcTemplate jdbcTemplate;UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}@Autowiredpublic void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}@Beanpublic PasswordEncoder passwordEncoder() {retu new BCryptPasswordEncoder();}/*open id */@Beanpublic JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {retu OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}/*open id */@Beanpublic JwtEncoder jwtEncoder() {retu new NimbusJwtEncoder(jwkSource());}@Beanpublic OAuth2TokenGenerator<?> tokenGenerator() {JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder());jwtGenerator.setJwtCustomizer(jwtCustomizer());OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();retu new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator);}@Beanpublic OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {retu context -> {JwsHeader.Builder headers = context.getHeaders();JwtClaimsSet.Builder claims = context.getClaims();Map<String, Object> map = claims.build().getClaims();if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {// Customize headers/claims for access_token//headers.header("customerHeader", "这是一个自定义header");//claims.claim("customerClaim", "这是一个自定义Claim");String useame = (String) map.get("sub");String sql = "select avatar, url from oauth_demo.oauth2_user where useame = ?";UserEntity userEntity = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(UserEntity.class), useame);Optional<UserEntity> userEntityOptional = Optional.ofNullable(userEntity);if (userEntityOptional.isPresent()) {claims.claim("url", userEntityOptional.get().getUrl());claims.claim("avatar", userEntityOptional.get().getAvatar());}}};}/** * 端点的 Spring Security 过滤器链 * @param http * @retu * @throws Exception */@Beanpublic SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)throws Exception {OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =new OAuth2AuthorizationServerConfigurer<>();/*open id */authorizationServerConfigurer.oidc(oidc -> {// 用户信息oidc.userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint.userInfoMapper(oidcUserInfoAuthenticationContext -> {String useame = oidcUserInfoAuthenticationContext.getAuthorization().getPrincipalName();String sql = "select url from oauth_demo.oauth2_user where useame = ?";UserEntity userEntity = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(UserEntity.class), useame);Optional<UserEntity> userEntityOptional = Optional.ofNullable(userEntity);Map<String, Object> claims = new HashMap<>();if (userEntityOptional.isPresent()) {claims.put("url", userEntity.getUrl());}claims.put("sub", useame);retu new OidcUserInfo(claims);}));// 客户端注册oidc.clientRegistrationEndpoint(Customizer.withDefaults());});http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);/*open id */RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();http.requestMatcher(endpointsMatcher).authorizeRequests(authorizeRequests ->authorizeRequests.anyRequest().authenticated()).userDetailsService(userService).csrf(AbstractHttpConfigurer::disable).apply(authorizationServerConfigurer);//未通过身份验证时重定向到登录页面授权端点http.exceptionHandling((exceptions) -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")));retu http.build();}/** * 用于身份验证的 Spring Security 过滤器链 * @param http * @retu * @throws Exception */@Beanpublic SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)throws Exception {http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())//表单登录处理从授权服务器过滤器链.formLogin(Customizer.withDefaults());retu http.build();}/** * 返回注册客户端资源,注意这里采用的是内存模式,后续可以改成jdbc模式。RegisteredClientRepository用于管理客户端的实例。 * @retu */@Beanpublic RegisteredClientRepository registeredClientRepository() {//RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())//.clientId("messaging-client")//.clientSecret(passwordEncoder().encode("secret"))//.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)//.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)//.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)//.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)//.redirectUri("http://www.baidu.com")//.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")//.scope(OidcScopes.OPENID)//.scope("message.read")//.scope("message.write")//.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())//.tokenSettings(TokenSettings.builder()//// token有效期100分钟//.accessTokenTimeToLive(Duration.ofMinutes(100L))//// 使用默认JWT相关格式//.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)//// 开启刷新token//.reuseRefreshTokens(true)//// refreshToken有效期120分钟//.refreshTokenTimeToLive(Duration.ofMinutes(120L))//.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256).build()//)//.build();JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);//RegisteredClient client = registeredClientRepository.findByClientId("messaging-client");//Optional<RegisteredClient> clientOptional = Optional.ofNullable(client);//if (clientOptional.isEmpty()) {//registeredClientRepository.save(registeredClient);//}retu registeredClientRepository;}@Beanpublic OAuth2AuthorizationService authorizationService() {retu new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository());}/** * 授权确认信息处理服务 */@Beanpublic OAuth2AuthorizationConsentService authorizationConsentService() {retu new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository());}/** * 生成jwk资源,com.nimbusds.jose.jwk.source.JWKSource用于签署访问令牌的实例。 * @retu */@Beanpublic JWKSource<SecurityContext> jwkSource() {KeyPair keyPair = generateRsaKey();RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();JWKSet jwkSet = new JWKSet(rsaKey);retu new ImmutableJWKSet<>(jwkSet);}/** * 生成密钥对,启动时生成的带有密钥的实例java.security.KeyPair用于创建JWKSource上述内容 * @retu */private static KeyPair generateRsaKey() {KeyPair keyPair;try {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);keyPair = keyPairGenerator.generateKeyPair();}catch (Exception ex) {throw new IllegalStateException(ex);}retu keyPair;}/** * ProviderSettings配置 Spring Authorization Server的实例 * @retu */@Beanpublic ProviderSettings providerSettings() {retu ProviderSettings.builder().build();}}
UserService
用户登录时使用:
package com.example.code.authorization.service;import com.example.code.authorization.entity.UserEntity;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;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.UseameNotFoundException;import org.springframework.stereotype.Component;import java.util.Collections;import java.util.Optional;@Componentpublic class UserService implements UserDetailsService {JdbcTemplate jdbcTemplate;@Autowiredpublic void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}@Overridepublic UserDetails loadUserByUseame(String useame) throws UseameNotFoundException {String sql = "select id, useame, password from oauth_demo.oauth2_user where useame = ?";UserEntity user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(UserEntity.class), useame);Optional<UserEntity> userOptional = Optional.ofNullable(user);if (userOptional.isEmpty()) {throw new UseameNotFoundException("user is not exist");}retu new User(userOptional.get().getUseame(), userOptional.get().getPassword(), true,true,true,true, Collections.emptyList());}}
关于OIDC
OIDC是在OAuth2协议基础上的一个认证层,通过使用access_token调用/userinfo接口获取一些用户信息。这个/userinfo接口只能给open_id的scope请求调用,其他scope的请求是无法调用这个接口的。同时,生成access_token请求的响应相对于普通的响应,会多出一个id_token,这个id_token可以用于解析身份信息:
@Beanpublic OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {retu context -> {JwsHeader.Builder headers = context.getHeaders();JwtClaimsSet.Builder claims = context.getClaims();Map<String, Object> map = claims.build().getClaims();if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {// Customize headers/claims for access_token// headers.header("customerHeader", "这是一个自定义header");// claims.claim("customerClaim", "这是一个自定义Claim");String useame = (String) map.get("sub");String sql = "select avatar, url from oauth_demo.oauth2_user where useame = ?";UserEntity userEntity = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(UserEntity.class), useame);Optional<UserEntity> userEntityOptional = Optional.ofNullable(userEntity);if (userEntityOptional.isPresent()) {claims.claim("url", userEntityOptional.get().getUrl());claims.claim("avatar", userEntityOptional.get().getAvatar());}}};}
对id_token的解析是直接在前端进行的,这个token不能用于后端接口的权限验证,作用仅仅只是储存一些信息,例如用户性别、头像等信息:
parseJwt(token) {let strings = token.split("."); //截取token,获取载体this.jwt = JSON.parse(window.atob(strings[1].replace(/-/g, "+").replace(/_/g, "/")))},
本文来自博客园,作者:imissinstagram,转载请注明原文链接:https://www.cnblogs.com/LostSecretGarden/p/16741249.html
作者:imissinstagram
来源链接:https://www.cnblogs.com/LostSecretGarden/p/16741249.html
版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。
2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。