旧依赖的移除

长久以来,使用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 {
@Bean
public SecurityFilterChain httpSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().anyRequest().authenticated()
.and().cors()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().oauth2ResourceServer().jwt();
return httpSecurity.build();
}
}

由于前后端分离,所以将服务端的session策略设置为无状态。

配置文件

jwt模式

配置文件需要指定认证服务器的地址和认证的方式,在jwt模式下,资源服务器会请求认证服务器的/oauth2/jwks端点,拿到公钥以后对jwt进行验证。

server:
port: 9600
spring:
datasource:
username: root
password: 12345678
driver-class-name: com.mysql.cj.jdbc.Driver
url: 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 {
return 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());
return 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; @RestController
public class UserController { JdbcTemplate jdbcTemplate; @Autowired
public 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 username = authentication.getName();
String userSql = "select * from oauth2_user where username = ?";
UserEntity user = jdbcTemplate.queryForObject(userSql, new BeanPropertyRowMapper<>(UserEntity.class), username);
String queryClientSql = "select * from oauth2_authorization_consent where principal_name = ?";
UserClientEntity userClientEntity = jdbcTemplate.queryForObject(queryClientSql, new BeanPropertyRowMapper<>(UserClientEntity.class), username);
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);
return 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.setAllowedOriginPatterns(allowedOrigins);
corsConfig.setExposedHeaders(exposedHeaders);
corsConfig.setMaxAge(36000L);
corsConfig.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return source;
}
}

配置文件

不需要额外的配置。

server:
port: 9500 spring:
application:
name: Code-Authorization
datasource:
username: root
password: 12345678
driver-class-name: com.mysql.cj.jdbc.Driver
url: 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.*; @Configuration
public class SecurityConfig { JdbcTemplate jdbcTemplate; UserService userService; @Autowired
public void setUserService(UserService userService) {
this.userService = userService;
} @Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
} @Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} /*
open id
*/
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
/*
open id
*/ @Bean
public JwtEncoder jwtEncoder() {
return new NimbusJwtEncoder(jwkSource());
} @Bean
public OAuth2TokenGenerator<?> tokenGenerator() {
JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder());
jwtGenerator.setJwtCustomizer(jwtCustomizer());
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
return new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
} @Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return 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 username = (String) map.get("sub");
String sql = "select avatar, url from oauth_demo.oauth2_user where username = ?";
UserEntity userEntity = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(UserEntity.class), username);
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
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception { OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>(); /*
open id
*/
authorizationServerConfigurer
.oidc(oidc -> {
// 用户信息
oidc.userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint.userInfoMapper(oidcUserInfoAuthenticationContext -> {
String username = oidcUserInfoAuthenticationContext.getAuthorization().getPrincipalName();
String sql = "select url from oauth_demo.oauth2_user where username = ?";
UserEntity userEntity = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(UserEntity.class), username);
Optional<UserEntity> userEntityOptional = Optional.ofNullable(userEntity);
Map<String, Object> claims = new HashMap<>();
if (userEntityOptional.isPresent()) {
claims.put("url", userEntity.getUrl());
}
claims.put("sub", username);
return 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"))
); return http.build();
} /**
* 用于身份验证的 Spring Security 过滤器链
* @param http
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
//表单登录处理从授权服务器过滤器链
.formLogin(Customizer.withDefaults()); return http.build();
} /**
* 返回注册客户端资源,注意这里采用的是内存模式,后续可以改成jdbc模式。RegisteredClientRepository用于管理客户端的实例。
* @return
*/
@Bean
public 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);
// }
return registeredClientRepository;
} @Bean
public OAuth2AuthorizationService authorizationService() {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository());
} /**
* 授权确认信息处理服务
*/
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService() {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository());
} /**
* 生成jwk资源,com.nimbusds.jose.jwk.source.JWKSource用于签署访问令牌的实例。
* @return
*/
@Bean
public 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);
return new ImmutableJWKSet<>(jwkSet);
} /**
* 生成密钥对,启动时生成的带有密钥的实例java.security.KeyPair用于创建JWKSource上述内容
* @return
*/
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);
}
return keyPair;
} /**
* ProviderSettings配置 Spring Authorization Server的实例
* @return
*/
@Bean
public ProviderSettings providerSettings() {
return 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.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.Optional; @Component
public class UserService implements UserDetailsService { JdbcTemplate jdbcTemplate; @Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
} @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String sql = "select id, username, password from oauth_demo.oauth2_user where username = ?";
UserEntity user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(UserEntity.class), username);
Optional<UserEntity> userOptional = Optional.ofNullable(user);
if (userOptional.isEmpty()) {
throw new UsernameNotFoundException("user is not exist");
}
return new User(userOptional.get().getUsername(), 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可以用于解析身份信息:

@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return 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 username = (String) map.get("sub");
String sql = "select avatar, url from oauth_demo.oauth2_user where username = ?";
UserEntity userEntity = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(UserEntity.class), username);
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, "/")))
},

spring-security-oauth2-authorization-server的更多相关文章

  1. spring security oauth2 架构---官方

    原文地址:https://projects.spring.io/spring-security-oauth/docs/oauth2.html Introduction This is the user ...

  2. Spring Cloud(6.1):搭建OAuth2 Authorization Server

    配置web.xml 添加spring-cloud-starter-security和spring-security-oauth2-autoconfigure两个依赖. </dependency& ...

  3. 【OAuth2.0】Spring Security OAuth2.0篇之初识

    不吐不快 因为项目需求开始接触OAuth2.0授权协议.断断续续接触了有两周左右的时间.不得不吐槽的,依然是自己的学习习惯问题,总是着急想了解一切,习惯性地钻牛角尖去理解小的细节,而不是从宏观上去掌握 ...

  4. Spring security oauth2最简单入门环境搭建

    关于OAuth2的一些简介,见我的上篇blog:http://wwwcomy.iteye.com/blog/2229889 PS:貌似内容太水直接被鹳狸猿干沉.. 友情提示 学习曲线:spring+s ...

  5. Spring Security Oauth2系列(一)

    前言: 关于oauth2,其实是一个规范,本文重点讲解spring对他进行的实现,如果你还不清楚授权服务器,资源服务器,认证授权等基础概念,可以移步理解OAuth 2.0 - 阮一峰,这是一篇对于oa ...

  6. Spring Security OAuth2 SSO

    通常公司肯定不止一个系统,每个系统都需要进行认证和权限控制,不可能每个每个系统都自己去写,这个时候需要把登录单独提出来 登录和授权是统一的 业务系统该怎么写还怎么写 最近学习了一下Spring Sec ...

  7. Spring Security Oauth2 的配置

    使用oauth2保护你的应用,可以分为简易的分为三个步骤 配置资源服务器 配置认证服务器 配置spring security 前两点是oauth2的主体内容,但前面我已经描述过了,spring sec ...

  8. spring security oauth2 client_credentials模

    spring security oauth2 client_credentials模 https://www.jianshu.com/p/1c3eea71410e 序 本文主要简单介绍一下spring ...

  9. springboot+spring security +oauth2.0 demo搭建(password模式)(认证授权端与资源服务端分离的形式)

    项目security_simple(认证授权项目) 1.新建springboot项目 这儿选择springboot版本我选择的是2.0.6 点击finish后完成项目的创建 2.引入maven依赖  ...

  10. Spring Security Oauth2 单点登录案例实现和执行流程剖析

    Spring Security Oauth2 OAuth是一个关于授权的开放网络标准,在全世界得到的广泛的应用,目前是2.0的版本.OAuth2在“客户端”与“服务提供商”之间,设置了一个授权层(au ...

随机推荐

  1. .net core 序列日志Serilog

    Serilog 是一个用于 .NET 应用程序的诊断日志库.它易于设置,具有干净的 API,并且可以在所有最新的 .NET 平台上运行.尽管即使在最简单的应用程序中它也很有用,但 Serilog 对结 ...

  2. Maven简答题

    1.什么是Maven? 自动化构建工具,专注服务于Java平台的项目构建和依赖管理 2.使用Maven的好处以及原因? (1)大量的jar包反复复制,造成冗余.使用Maven后每个jar包只在本地仓库 ...

  3. Nginx教程由浅入深

    Nginx   一.安装Nginx 1.准备工作 (1)打开虚拟机,使用远程连接工具连接 linux 操作系统 (2)到 nginx 官网下载软件 http://nginx.org/ 2.开始进行 n ...

  4. global 函数

    x = 15 # 全局变量Gdef func_a(): print(x)def func_b(): print(x)def func_c(): global x # 在定义函数内声明x为全局变量后,才 ...

  5. oracle 索引创建

    --查询表里的索引 select t.*,i.index_type from user_ind_columns t,user_indexes i where t.index_name = i.inde ...

  6. 安装使用反编译工具ILSPY

    一.ILSPY简介1.1.ILSPY介绍 ILSPY是一款开源.免费的.且适用于.NET平台反编译[C#语言编写的程序和库(.dll)内容]工具:可以集成在Visual Studio 开发工具中,能够 ...

  7. unity 2D 物体跟随鼠标旋转 移动

    using System.Collections; using System.Collections.Generic; using UnityEngine; public class FollowMo ...

  8. 直接使用Arrays.asList()转数组,转变类型实际为AbstractList

    1.直接将数组转换为list时List的类型为AbstractList public static void main(String[] args) { String[] arr = {"A ...

  9. 用dig或nslookup命令查询txt解析记录

    这几天想把HTTPS装上,阿里云的免费证书需要在域名解析的地方添加TXT记录.文档里用的是dig命令,我本地装了nslookup.试验下如何用咯. dig命令 dig用法很多,这里只使用dig txt ...

  10. C# List GroupBy and Sum

    List<PartRequest> partRequests = new List<PartRequest>(); partRequests.Add(new PartReque ...