微服务OAuth 2.1认证授权Demo方案(Spring Security 6)

时间:2024-02-19 22:10:21
package com.xuecheng.auth.config; 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.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.MediaType; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; 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.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.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; 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.List; import java.util.UUID; import java.util.stream.Collectors; /** * 身份验证服务器安全配置 * * @author mumu * @date 2024/02/13 */ //@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) @Configuration @EnableWebSecurity public class AuthServerSecurityConfig { 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; } /** * 密码编码器 * 用于加密认证服务器client密码和用户密码 * * @return {@link PasswordEncoder} */ @Bean public PasswordEncoder passwordEncoder() { // 密码为明文方式 // return NoOpPasswordEncoder.getInstance(); // 或使用 BCryptPasswordEncoder return new BCryptPasswordEncoder(); } /** * 授权服务器安全筛选器链 * <br/> * 来自Spring Authorization Server示例,用于暴露Oauth2.1端点,一般不影响常规的请求 * * @param http http * @return {@link SecurityFilterChain} * @throws Exception 例外 */ @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) .oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0 http // Redirect to the login page when not authenticated from the // authorization endpoint .exceptionHandling((exceptions) -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) ) ) // Accept access tokens for User Info and/or Client Registration .oauth2ResourceServer((resourceServer) -> resourceServer .jwt(Customizer.withDefaults())); return http.build(); } /** * 默认筛选器链 * <br/> * 这个才是我们需要关心的过滤链,可以指定哪些请求被放行,哪些请求需要JWT验证 * * @param http http * @return {@link SecurityFilterChain} * @throws Exception 例外 */ @Bean @Order(2) public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .requestMatchers(new AntPathRequestMatcher("/actuator/**")).permitAll() .requestMatchers(new AntPathRequestMatcher("/login")).permitAll() .requestMatchers(new AntPathRequestMatcher("/logout")).permitAll() .requestMatchers(new AntPathRequestMatcher("/wxLogin")).permitAll() .requestMatchers(new AntPathRequestMatcher("/register")).permitAll() .requestMatchers(new AntPathRequestMatcher("/oauth2/**")).permitAll() .requestMatchers(new AntPathRequestMatcher("/**/*.html")).permitAll() .requestMatchers(new AntPathRequestMatcher("/**/*.json")).permitAll() .requestMatchers(new AntPathRequestMatcher("/auth/**")).permitAll() .anyRequest().authenticated() ) .csrf(AbstractHttpConfigurer::disable) //指定logout端点,用于退出登陆,不然二次获取授权码时会自动登陆导致短时间内无法切换用户 .logout(logout -> logout .logoutUrl("/logout") .addLogoutHandler(new SecurityContextLogoutHandler()) .logoutSuccessUrl("http://www.51xuecheng.cn") ) .formLogin(Customizer.withDefaults()) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()) // .jwt(jwt -> jwt // .jwtAuthenticationConverter(jwtAuthenticationConverter()) // ) ); return http.build(); } private JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter(); return jwtConverter; } /** * 客户端管理实例 * <br/> * 来自Spring Authorization Server示例 * * @return {@link RegisteredClientRepository} */ @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("XcWebApp") .clientSecret(passwordEncoder().encode("XcWebApp")) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .redirectUri("http://www.51xuecheng.cn") .redirectUri("http://localhost:63070/auth/wxLogin") .redirectUri("http://www.51xuecheng.cn/sign.html") // .postLogoutRedirectUri("http://localhost:63070/login?logout") .scope("all") .scope(OidcScopes.OPENID) .scope(OidcScopes.PROFILE) .scope("message.read") .scope("message.write") .scope("read") .scope("write") .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) .tokenSettings(TokenSettings.builder() .accessTokenTimeToLive(Duration.ofHours(2)) // 设置访问令牌的有效期 .refreshTokenTimeToLive(Duration.ofDays(3)) // 设置刷新令牌的有效期 .reuseRefreshTokens(true) // 是否重用刷新令牌 .build()) .build(); return new InMemoryRegisteredClientRepository(registeredClient); } /** * jwk源 * <br/> * 对访问令牌进行签名的示例,里面包含公私钥信息。 * * @return {@link JWKSource}<{@link SecurityContext}> */ @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); } /** * jwt解码器 * <br/> * JWT解码器,主要就是基于公钥信息来解码 * * @param jwkSource jwk源 * @return {@link JwtDecoder} */ @Bean public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().build(); } /** * JWT定制器 * <BR/> * 可以往JWT从加入额外信息,这里是加入authorities字段,是一个权限数组。 * * @return {@link OAuth2TokenCustomizer}<{@link JwtEncodingContext}> */ @Bean public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() { return context -> { Authentication authentication = context.getPrincipal(); if (authentication.getPrincipal() instanceof UserDetails userDetails) { List<String> authorities = userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()); context.getClaims().claim("authorities", authorities); } }; } }