环境:
spring boot 3.0.0
java 19
spring-authorization-server 1.0.0
mysql 8.0.26
1.oauth2的配置类
import com.example.oauth2.jose.Jwks;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
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.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
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.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
@Configuration(proxyBeanMethods = false)
public class Oauth2Configuration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
// @formatter:off
http
.exceptionHandling(exceptions ->
exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// @formatter:on
return http.build();
}
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
return new JdbcRegisteredClientRepository(jdbcTemplate);
}
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = Jwks.generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
}
2.定义生成的token返回类(可选)
@Builder
@Getter
public class SSOTokenResponse {
private String access_token;
private String refresh_token;
private String scope;
private String token_type;
private Long expires_in;
}
3.生成token(本文中主要说的就是这个类)
import com.example.oauth2.sso.token.entity.SSOToken;
import com.example.oauth2.sso.token.repository.SSOTokenRepository;
import com.example.oauth2.sso.token.response.SSOTokenResponse;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
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.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Service;
import java.security.Principal;
import java.util.*;
@Service
public record SSOTokenService(
RegisteredClientRepository jdbcRegisteredClientRepository,
OAuth2AuthorizationService authorizationService, HttpServletRequest request,
JWKSource<SecurityContext> jwkSource) {
public SSOTokenResponse generateTokenByUserAndClientId(User user, String clientId){
//清除掉用户密码
user.eraseCredentials();
//select 已注册客户端
RegisteredClient registeredClient = jdbcRegisteredClientRepository.findByClientId(clientId);
if (null == registeredClient){
throw new RuntimeException("client not exist");
}
//generate authentication
var details = new WebAuthenticationDetails(request.getRemoteHost(),null);
var principal = new OAuth2ClientAuthenticationToken(
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC,registeredClient.getClientSecret()
);
principal.setDetails(details);
principal.setAuthenticated(true);
//access token
OAuth2TokenContext context = DefaultOAuth2TokenContext.builder()
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
.registeredClient(registeredClient)
.principal(principal)
.build();
NimbusJwtEncoder encoder = new NimbusJwtEncoder(jwkSource);
var generatedAccessToken = new JwtGenerator(encoder).generate(context);
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), context.getAuthorizedScopes());
//refresh token
context = DefaultOAuth2TokenContext.builder().tokenType(OAuth2TokenType.REFRESH_TOKEN).registeredClient(registeredClient).build();
var refreshGenerator = new OAuth2RefreshTokenGenerator();
var refreshToken = (OAuth2RefreshToken)refreshGenerator.generate(context);
//保存token到oauth2_authorization表。如果不保存,refresh token将无法使用
var passwordPrincipal = new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
passwordPrincipal.setDetails(details);
var builder =OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(passwordPrincipal.getName())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.attribute(Principal.class.getName(), passwordPrincipal);
authorizationService.save(builder
.id(UUID.randomUUID().toString())
.accessToken(accessToken)
.refreshToken(refreshToken)
.build());
//返回token
return SSOTokenResponse.builder()
.access_token(accessToken.getTokenValue())
.token_type(accessToken.getTokenType().getValue())
.expires_in(accessToken.getExpiresAt().getEpochSecond() - accessToken.getIssuedAt().getEpochSecond())
.refresh_token(refreshToken.getTokenValue())
.build();
}
}
4.使用demo(可选)
@RestController
@RequestMapping("/oauth2/sso")
@RequiredArgsConstructor
public class SSOTokenController {
private final SSOTokenService ssoTokenService;
private final UserService userService;
@GetMapping("/token")
public ResponseEntity getToken(@RequestParam("mobile") String mobile, @RequestParam("clientId") String clientId){
try{
User user = userService.getByMobile(mobile);
SSOTokenResponse token =
ssoTokenService.generateTokenByUserAndClientId(
new org.springframework.security.core.userdetails.User(
user.getMobile(),
user.getPassword(),
!user.getDisabled(),
true,
true,
!user.getLocked(),
user.getRoles().stream().map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList())
), clientId);
return ResponseEntity.ok(SimpleResponse.ok(token));
}catch (RuntimeException r){
return ResponseEntity.ok(SimpleResponse.fail(1, r.getMessage()));
}
}
}
5.返回类SimpleResponse(非必须)
@Getter
@Setter
@AllArgsConstructor
public class SimpleResponse<T>{
private Integer code;
private String message;
private T value;
private SimpleResponse(){}
public static <T> SimpleResponse<T> ok(T t){
return new SimpleResponse(0,"成功", t);
}
public static SimpleResponse success(){
return new SimpleResponse(0,"成功", null);
}
public static SimpleResponse fail(Integer code, String message){
return new SimpleResponse(code, message, null);
}
}