旧依赖的移除

长久以来,使用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. MargeSort

    归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.将已有序的子序列合并,得到完全有序的序列: ...

  2. 专业视频图片水印清除工具丨HitPaw Watermark Remover 2.1.3

    软件下载 MAC/Windows HitPaw Watermark Remover 软件介绍 HitPaw Watermark Remover 是一款能够轻松去除图片视频水印的软件.对于有水印的图片视 ...

  3. Java pom阿里云插件

    <pluginRepositories> <pluginRepository> <id>alimaven spring plugin</id> < ...

  4. linux 挂载移动硬盘

    fdisk -l mkdir -p /mnt/usbhd1 mount -t ntfs /dev/sdc1 /mnt/usbhd1 # 挂载 umount /mnt/usbhd1 # 解挂载 http ...

  5. git 命令 使用记录

    这是ider 在pull 代码是报的异常大概意思是本机有未处理的合并可是点击view files都之前删除的一些文件 这种问题可以使用一下命令 git fetch --all && g ...

  6. 【Java学习Day11】变量种类及命名规范

    变量 变量是什么:就是可以变化的量 Java是一种强类型语言,每个变量都必须声明其类型 Java变量是程序中最基本的存储单元,其要素包括变量名,变量类型和作用域 type varName [=valu ...

  7. Using Yocto Project with BeagleBone Black 英文版、Using Yocto Project with BeagleBone Black中文版

    Using Yocto Project with BeagleBone Black  手册英文版.中文版 https://market.m.taobao.com/app/idleFish-F2e/wi ...

  8. 实现ViewPager一次滑动多页(保持居中)

    项目中开发日历功能,需求是可以连续滑动多页,有列表的流畅.又要保持当前页居中显示. 参考文献:  http://www.open-open.com/lib/view/open1435026935638 ...

  9. jeecgboot <j-popup

    <a-col :span="24"> <a-form-item label=" 规格" :labelCol="labelCol&qu ...

  10. 一键部署redis-5.0.5

    #!/bin/bash echo "-------------------------------------" echo -e "\e[1;33m 下面开始部署redi ...