基于上一篇文章流程介绍,这一篇文章将会介绍如何通过 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
评论