• Spring Security对接OIDC(OAuth2)外部认证


    前后端分离项目对接OIDC(OAuth2)外部认证,认证服务器可以使用Keycloak。

    后端已有用户管理和权限管理,需要外部认证服务器的用户名和业务系统的用户名一致才可以登录。

    后台基于Spring Boot 2.7 + Spring Security

    流程:

    1. 前台浏览器跳转到  后台地址 + /login/oauth2/authorization/my-oidc-client
    2. 后台返回302重定向,重定向到登录外部认证服务器 http://my-oidc-provider.com
    3. 在外部认证网页登录成功后,自动重定向到前台地址 http://localhost/login ,前台取得URL中的参数,使用这些参数发送ajax post请求到  后台地址 + /login/oauth2/my-oidc-client
    4. 后台会自动与 http://my-oidc-provider.com 交互完成登录,并使用用户名取得本地用户信息后返回json
    5. 如果第4步失败,则返回自定义的错误信息json
       

    (1)引入依赖:

    org.springframework.boot:spring-boot-starter-oauth2-client

    (2)配置文件:

    1. spring.security.oauth2.client.registration.my-oidc-client.provider=my-oidc-provider
    2. spring.security.oauth2.client.registration.my-oidc-client.client-id=my-client-id
    3. spring.security.oauth2.client.registration.my-oidc-client.client-secret=my-client-secret
    4. spring.security.oauth2.client.registration.my-oidc-client.authorization-grant-type=authorization_code
    5. spring.security.oauth2.client.registration.my-oidc-client.scope=openid,profile
    6. spring.security.oauth2.client.registration.my-oidc-client.redirect-uri=http://localhost/login
    7. spring.security.oauth2.client.provider.my-oidc-provider.issuer-uri=http://my-oidc-provider.com

    (3)Spring Security配置类:

    1. package com.example.demo.config;
    2. import java.io.IOException;
    3. import javax.servlet.ServletException;
    4. import javax.servlet.http.HttpServletRequest;
    5. import javax.servlet.http.HttpServletResponse;
    6. import org.springframework.context.annotation.Bean;
    7. import org.springframework.context.annotation.Configuration;
    8. import org.springframework.core.convert.converter.Converter;
    9. import org.springframework.security.config.annotation.ObjectPostProcessor;
    10. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    11. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    12. import org.springframework.security.core.Authentication;
    13. import org.springframework.security.core.AuthenticationException;
    14. import org.springframework.security.core.userdetails.UserDetails;
    15. import org.springframework.security.core.userdetails.UserDetailsService;
    16. import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
    17. import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
    18. import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
    19. import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
    20. import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
    21. import org.springframework.security.oauth2.core.oidc.user.OidcUser;
    22. import org.springframework.security.web.SecurityFilterChain;
    23. import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
    24. import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
    25. import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
    26. import com.fasterxml.jackson.core.JsonProcessingException;
    27. import com.fasterxml.jackson.databind.ObjectMapper;
    28. @Configuration
    29. @EnableWebSecurity
    30. public class MyOAuth2SecurityConfig {
    31. private final UserDetailsService userDetailsService;
    32. private final ClientRegistrationRepository clientRegistrationRepository;
    33. private final ObjectMapper objectMapper;
    34. public MyOAuth2SecurityConfig(UserDetailsService userDetailsService, ClientRegistrationRepository clientRegistrationRepository, ObjectMapper objectMapper) {
    35. this.userDetailsService = userDetailsService;
    36. this.clientRegistrationRepository = clientRegistrationRepository;
    37. this.objectMapper = objectMapper;
    38. }
    39. @Bean
    40. public SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception {
    41. http
    42. .authorizeHttpRequests(httpRequests -> httpRequests.anyRequest().authenticated())
    43. .oauth2Login(oauth2Login -> oauth2Login
    44. .authorizationEndpoint(authorization -> authorization
    45. .baseUri("/login/oauth2/authorization"))
    46. .loginProcessingUrl("/login/oauth2/*")
    47. .successHandler(new MyAuthenticationSuccessHandler())
    48. .failureHandler(new MyAuthenticationFailureHandler())
    49. .addObjectPostProcessor(new ObjectPostProcessor() {
    50. @Override
    51. public extends OAuth2LoginAuthenticationFilter> O postProcess(O filter) {
    52. filter.setAuthenticationResultConverter(new Converter() {
    53. @Override
    54. public OAuth2AuthenticationToken convert(OAuth2LoginAuthenticationToken source) {
    55. OidcUser user = (OidcUser) source.getPrincipal();
    56. String userName = user.getAttribute("preferred_username");
    57. // 根据用户名获取适用于本系统的用户对象,用户对象同时实现UserDetails和OidcUser接口
    58. UserDetails myUser = userDetailsService.loadUserByUsername(userName);
    59. // 用户对象保存IdToken用于退出登录
    60. // myUser.setIdToken(user.getIdToken());
    61. return new OAuth2AuthenticationToken((OidcUser) myUser, myUser.getAuthorities(), source.getClientRegistration().getRegistrationId());
    62. }
    63. });
    64. return filter;
    65. }
    66. }))
    67. .formLogin(formLogin -> formLogin.disable())
    68. .logout(logout -> logout
    69. .logoutUrl("/logout")
    70. .invalidateHttpSession(true)
    71. .deleteCookies("SESSION")
    72. .logoutSuccessHandler(this.logoutSuccessHandler()))
    73. .csrf(csrf -> csrf.disable());
    74. return http.build();
    75. }
    76. private LogoutSuccessHandler logoutSuccessHandler() {
    77. OidcClientInitiatedLogoutSuccessHandler handler = new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
    78. handler.setPostLogoutRedirectUri(this.clientRegistrationRepository.findByRegistrationId("my-oidc-client").getRedirectUri());
    79. return handler;
    80. }
    81. class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    82. @Override
    83. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws JsonProcessingException, IOException {
    84. response.setStatus(HttpServletResponse.SC_OK);
    85. response.setContentType("application/json;charset=UTF-8");
    86. // 这里自定义返回的json对象内容
    87. response.getWriter().write(objectMapper.writeValueAsString(authentication.getPrincipal()));
    88. response.getWriter().flush();
    89. response.getWriter().close();
    90. }
    91. }
    92. class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    93. @Override
    94. public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
    95. AuthenticationException exception) throws IOException, ServletException {
    96. response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    97. response.setContentType("application/json;charset=UTF-8");
    98. // 这里自定义返回的json对象内容
    99. response.getWriter().write(objectMapper.writeValueAsString(""));
    100. response.getWriter().flush();
    101. response.getWriter().close();
    102. }
    103. }
    104. }

  • 相关阅读:
    mysql explain type 枚举
    Java 面试题经典 77 问(含答案)!
    cpp浅析STL-set
    Python简单实现人脸识别检测, 对照片进行评分
    SAP FI 项目号 系统状态CRTD是活动的 BS013
    数据库定时备份winserver2012篇
    一份简便的PyTorch教程,从不用自己配置环境开始。
    MySQL8 Group By 新特性
    c++ mutable
    Vue--Router--嵌套路由(children)的用法
  • 原文地址:https://blog.csdn.net/langzitianya/article/details/136175298