通过用户姓名和客户端id直接生成token

环境:
    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);
    }
}

猜你喜欢

转载自blog.csdn.net/miaowansheng/article/details/128131954
今日推荐