前后端分离项目对接OIDC(OAuth2)外部认证,认证服务器可以使用Keycloak。
后端已有用户管理和权限管理,需要外部认证服务器的用户名和业务系统的用户名一致才可以登录。
后台基于Spring Boot 2.7 + Spring Security
流程:
(1)引入依赖:
org.springframework.boot:spring-boot-starter-oauth2-client
(2)配置文件:
-
- spring.security.oauth2.client.registration.my-oidc-client.provider=my-oidc-provider
- spring.security.oauth2.client.registration.my-oidc-client.client-id=my-client-id
- spring.security.oauth2.client.registration.my-oidc-client.client-secret=my-client-secret
- spring.security.oauth2.client.registration.my-oidc-client.authorization-grant-type=authorization_code
- spring.security.oauth2.client.registration.my-oidc-client.scope=openid,profile
- spring.security.oauth2.client.registration.my-oidc-client.redirect-uri=http://localhost/login
- spring.security.oauth2.client.provider.my-oidc-provider.issuer-uri=http://my-oidc-provider.com
(3)Spring Security配置类:
- package com.example.demo.config;
-
- import java.io.IOException;
-
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.core.convert.converter.Converter;
- import org.springframework.security.config.annotation.ObjectPostProcessor;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.AuthenticationException;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
- import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
- import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
- import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
- import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
- import org.springframework.security.oauth2.core.oidc.user.OidcUser;
- import org.springframework.security.web.SecurityFilterChain;
- import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
- import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
- import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
-
- import com.fasterxml.jackson.core.JsonProcessingException;
- import com.fasterxml.jackson.databind.ObjectMapper;
-
- @Configuration
- @EnableWebSecurity
- public class MyOAuth2SecurityConfig {
-
- private final UserDetailsService userDetailsService;
- private final ClientRegistrationRepository clientRegistrationRepository;
- private final ObjectMapper objectMapper;
-
- public MyOAuth2SecurityConfig(UserDetailsService userDetailsService, ClientRegistrationRepository clientRegistrationRepository, ObjectMapper objectMapper) {
- this.userDetailsService = userDetailsService;
- this.clientRegistrationRepository = clientRegistrationRepository;
- this.objectMapper = objectMapper;
- }
-
- @Bean
- public SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception {
- http
- .authorizeHttpRequests(httpRequests -> httpRequests.anyRequest().authenticated())
- .oauth2Login(oauth2Login -> oauth2Login
- .authorizationEndpoint(authorization -> authorization
- .baseUri("/login/oauth2/authorization"))
- .loginProcessingUrl("/login/oauth2/*")
- .successHandler(new MyAuthenticationSuccessHandler())
- .failureHandler(new MyAuthenticationFailureHandler())
- .addObjectPostProcessor(new ObjectPostProcessor
() { - @Override
- public
extends OAuth2LoginAuthenticationFilter> O postProcess(O filter) { - filter.setAuthenticationResultConverter(new Converter
() { - @Override
- public OAuth2AuthenticationToken convert(OAuth2LoginAuthenticationToken source) {
- OidcUser user = (OidcUser) source.getPrincipal();
- String userName = user.getAttribute("preferred_username");
- // 根据用户名获取适用于本系统的用户对象,用户对象同时实现UserDetails和OidcUser接口
- UserDetails myUser = userDetailsService.loadUserByUsername(userName);
- // 用户对象保存IdToken用于退出登录
- // myUser.setIdToken(user.getIdToken());
- return new OAuth2AuthenticationToken((OidcUser) myUser, myUser.getAuthorities(), source.getClientRegistration().getRegistrationId());
- }
- });
- return filter;
- }
- }))
- .formLogin(formLogin -> formLogin.disable())
- .logout(logout -> logout
- .logoutUrl("/logout")
- .invalidateHttpSession(true)
- .deleteCookies("SESSION")
- .logoutSuccessHandler(this.logoutSuccessHandler()))
- .csrf(csrf -> csrf.disable());
-
- return http.build();
- }
-
- private LogoutSuccessHandler logoutSuccessHandler() {
- OidcClientInitiatedLogoutSuccessHandler handler = new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
- handler.setPostLogoutRedirectUri(this.clientRegistrationRepository.findByRegistrationId("my-oidc-client").getRedirectUri());
- return handler;
- }
-
- class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
- @Override
- public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws JsonProcessingException, IOException {
- response.setStatus(HttpServletResponse.SC_OK);
- response.setContentType("application/json;charset=UTF-8");
- // 这里自定义返回的json对象内容
- response.getWriter().write(objectMapper.writeValueAsString(authentication.getPrincipal()));
- response.getWriter().flush();
- response.getWriter().close();
- }
- }
-
- class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
- @Override
- public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
- AuthenticationException exception) throws IOException, ServletException {
- response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
- response.setContentType("application/json;charset=UTF-8");
- // 这里自定义返回的json对象内容
- response.getWriter().write(objectMapper.writeValueAsString(""));
- response.getWriter().flush();
- response.getWriter().close();
- }
- }
- }