写点什么

如何搭建一个专属的认证中心(二)

作者:Kevin_913
  • 2023-09-30
    广东
  • 本文字数:4211 字

    阅读完需:约 14 分钟

如何搭建一个专属的认证中心(二)

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

发布于: 刚刚阅读数: 5
用户头像

Kevin_913

关注

纸上得来终觉浅,绝知此事要躬行。 2019-02-25 加入

专注于代码和设计15+年。 主要涉及Java,Golang,云平台。

评论

发布
暂无评论
如何搭建一个专属的认证中心(二)_spring security_Kevin_913_InfoQ写作社区