基于上一篇文章流程介绍,这一篇文章将会介绍如何通过 springboot+spring security 来搭建在自定义的认证中心,包括自定义登录和认证页面。
创建工程:
认证中心是通过 springboot+spring security 来实现的,所以只需要导入 springboot 和 spring security 的依赖就可以了,需要注意的是 springboot 的版本,2.x 和 3.x 版本使用的 spring security 分别是 5.x 和 6.x,两个版本之间相差很大,网上大部分都是 2.x+5.x 的版本,我这里使用的是 3.x+6.x 的版本。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server</artifactId> </dependency>
复制代码
配置认证中心:
如果只是做一个认证中心,不需要修改任何的配置。
需要添加两个 filter:
1、登录的 filter,这里会指定白名单和 login 的页面配置。
@Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests( (authorize) -> authorize.requestMatchers(whiteList()).permitAll().anyRequest().authenticated()) .cors(Customizer.withDefaults()) .formLogin(Customizer.withDefaults());
return http.build(); }
复制代码
2、registerclient 的 filter,这里会指定授权页面以及权限认证失败的跳转。
@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 .exceptionHandling((exceptions) -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) ) );
return http.build(); }
复制代码
3、配置 jwt 和 passwordencorder,这里我使用官方提供的工具。
@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); }
@Bean public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); }
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
复制代码
4、自定义登录认证,需要实现 AuthenticationProvider 中的 authenticate 方法,下面是一个简单的数据库验证例子。
@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String userName = authentication.getName(); String password = authentication.getCredentials().toString(); User dbUser = userService.validate(userName, password); UserDetail detail = userService.getUser(dbUser.getUserId()); if (dbUser.getUserId() != 0) { return new UsernamePasswordAuthenticationToken(userName, passwordEncoder.encode(password), detail.getAuthorities()); } throw new BadCredentialsException("password is not correct, please check username and password."); }
复制代码
5、自定义授权验证,需要实现 RegisteredClientRepository 中的 findById 和 findByClientId 方法,需要返回的是一个 RegisteredClient,这个对象包含里 clientId、clientsecret、redirectUrl、scopes 等,需要注意的是这些字段要跟你保存的 RegisteredClient 保持一致或者是它的子集,下面是一个简单的代码。
@Override public RegisteredClient findByClientId(String clientId) { List<SampleRegisteredClient> clients = registeredClientService.getRegisteredClients();
for (SampleRegisteredClient client : clients) { if (client.getClientId().equals(clientId)) { return fromSampleRegisteredClient(client); } }
return null; }
private RegisteredClient fromSampleRegisteredClient(SampleRegisteredClient client) { RegisteredClient.Builder builder = RegisteredClient.withId("" + client.getId()) .clientId(client.getClientId()) .clientSecret(client.getClientSecret());
setAuthenticationMethods(builder, client.getAuthMethods()); setGrantTypes(builder, client.getGrantTypes()); setScope(builder, client.getScope());
builder.redirectUri(client.getCallback()) .tokenSettings(TokenSettings.builder().reuseRefreshTokens(true).setting("client-id", client.getClientId()).build()) .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build());
return builder.build(); }
复制代码
完成上述的配置以后,你这个认证中心就可以启动起来了,如果你需要自定义登录页面和自定义授权页面,还需要以下步骤。
6、修改 #1 和 #2 的配置。
@Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { ... .formLogin(httpSecurityFormLoginConfigurer());
return http.build(); }
private Customizer<FormLoginConfigurer<HttpSecurity>> httpSecurityFormLoginConfigurer() { return new Customizer<FormLoginConfigurer<HttpSecurity>>() { @Override public void customize(FormLoginConfigurer<HttpSecurity> httpSecurityFormLoginConfigurer) { httpSecurityFormLoginConfigurer.loginPage("/login");
} }; }
复制代码
@Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http .getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint.consentPage("/oauth2/consent")) .oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0 ...
return http.build(); }
复制代码
7、添加自定义页面,我这里使用了 thymeleaf 来写页面,添加一个 controller 让你指定的"/login"和"/oauth2/consent"是可访问的,在授权页面你可以传递可用的属性到页面。
@RequestMapping("/login") public String login(){ return "login"; } @GetMapping(value = "/oauth2/consent") public String consent(Principal principal, Model model, @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId, @RequestParam(OAuth2ParameterNames.SCOPE) String scope, @RequestParam(OAuth2ParameterNames.STATE) String state) {
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
assert registeredClient != null; model.addAttribute("clientId", clientId); model.addAttribute("clientName", registeredClient.getClientName()); model.addAttribute("state", state); model.addAttribute("requestScopes", scope.split(",")); model.addAttribute("principalName", principal.getName());
return "consent"; }
复制代码
认证接口信息:
你可以通过查看 AuthorizationServerSettings.Builder 来查看,里面包含了各种 endpoints。
public static Builder builder() { return new Builder() .authorizationEndpoint("/oauth2/authorize") .deviceAuthorizationEndpoint("/oauth2/device_authorization") .deviceVerificationEndpoint("/oauth2/device_verification") .tokenEndpoint("/oauth2/token") .jwkSetEndpoint("/oauth2/jwks") .tokenRevocationEndpoint("/oauth2/revoke") .tokenIntrospectionEndpoint("/oauth2/introspect") .oidcClientRegistrationEndpoint("/connect/register") .oidcUserInfoEndpoint("/userinfo") .oidcLogoutEndpoint("/connect/logout"); }
复制代码
通过上述步骤,你就可以实现一个完全自定义的认证中心了。
视频地址:如何搭建一个专属的认证中心(二)_哔哩哔哩_bilibili
评论