介绍

原理简介

spring security基本原理:通过使用过滤器实现拦截数据流,并对参数进行一系列的处理。 最终由FilterSecurityInterceptor类拦截,并对之前过滤器的异常做处理(具体可参考:https://blog.csdn.net/duanshengjie/article/details/79985196)。
image.png
此项目也是开发了一个Filter,并放在了认证链上的匿名用户过滤器前的位置,保证在处理第三方认证的时候可以准确判断是否已经有认证用户。当存在已认证的用户则进行绑定操作,不存在已认证的用户则进行登录验证操作。

此项目基于 justauth开发的认证并嫁接到 spring security 的认证上。一些内容为参考spring-security-social项目。

功能

  • 未认证用户经第三方认证后走认证流程
  • 已认证用户经第三方认证后走注册流程
  • 查看用户绑定记录
  • 解绑用户

    主要代码展示

    创建认证服务、对应的Userdetals、UserDetailsService、AuthenticationToken、可提供的第三方Provider认证和对应的过滤器Filter。

    第三方认证服务

    主要提供用户登录时获取token,当拦截到用户是要用以第三方登录时,会跳转到第三方去登录认证。 ```java package com.luoqiz.oauth2.justauth;

import com.luoqiz.oauth2.justauth.exception.JustauthAuthenticationRedirectException; import com.luoqiz.oauth2.justauth.provider.JustauthAuthenticationService; import com.luoqiz.oauth2.justauth.util.ConcurrentHashMapCacheUtils; import com.xkcoding.justauth.AuthRequestFactory; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthResponse; import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.request.AuthRequest; import me.zhyd.oauth.utils.AuthStateUtils; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;

@Slf4j @NoArgsConstructor @Data public abstract class AbstractJustauthAuthenticationService implements JustauthAuthenticationService {

  1. private String providerId;
  2. private AuthRequestFactory factory;
  3. public AbstractJustauthAuthenticationService(String providerId, AuthRequestFactory factory) {
  4. this.providerId = providerId;
  5. this.factory = factory;
  6. }
  7. public void afterPropertiesSet(AuthUser user) throws Exception {
  8. }
  9. //此处跳转到第三方认证
  10. @Override
  11. public JustauthAuthenticationToken getAuthToken(HttpServletRequest request, HttpServletResponse response) {
  12. //判断是否是第一次登录
  13. String code = request.getParameter("code");
  14. if (!StringUtils.hasText(code)) {
  15. AuthRequest authRequest = factory.get(providerId);
  16. String state = providerId + "::" + AuthStateUtils.createState();
  17. Authentication auth = SecurityContextHolder.getContext().getAuthentication();
  18. if (auth != null) {
  19. log.info("auth user : {}", auth.toString());
  20. ConcurrentHashMapCacheUtils.setCache(state, auth, 120 * 1000);
  21. }
  22. throw new JustauthAuthenticationRedirectException(authRequest.authorize(state));
  23. } else if (StringUtils.hasText(code)) {
  24. try {
  25. AuthRequest authRequest = factory.get(providerId);
  26. AuthCallback callback = new AuthCallback();
  27. callback.setCode(code);
  28. String state = request.getParameter("state");
  29. callback.setState(state);
  30. Authentication auth = (Authentication) ConcurrentHashMapCacheUtils.getCache(state);
  31. if (auth != null) {
  32. log.info("auth user ---- : {}", auth.toString());
  33. SecurityContextHolder.getContext().setAuthentication(auth);
  34. }
  35. AuthUser user = parseUser(authRequest.login(callback));
  36. afterPropertiesSet(user);
  37. return new JustauthAuthenticationToken(providerId, user, null, false);
  38. } catch (Exception e) {
  39. log.debug("failed to exchange for access", e);
  40. return null;
  41. }
  42. } else {
  43. return null;
  44. }
  45. }
  46. protected AuthUser parseUser(AuthResponse<S> response) {
  47. if (response.getCode() == 2000) {
  48. AuthUser authuser = response.getData();
  49. return authuser;
  50. }
  51. return null;
  52. }

}

  1. <br />注意:此时需要跳转到第三方去登录认证的时候,若是已经登录,则会临时保存用户信息。是使用系统内自定义的线程。
  2. ```java
  3. package com.luoqiz.oauth2.justauth.service;
  4. import com.luoqiz.oauth2.justauth.AbstractJustauthAuthenticationService;
  5. import com.xkcoding.justauth.AuthRequestFactory;
  6. import lombok.extern.slf4j.Slf4j;
  7. import me.zhyd.oauth.model.AuthUser;
  8. @Slf4j
  9. public class GithubJustauthAuthenticationService<A> extends AbstractJustauthAuthenticationService<AuthUser> {
  10. public GithubJustauthAuthenticationService(String providerId, AuthRequestFactory factory) {
  11. super(providerId,factory );
  12. }
  13. }

认证token

  1. /*
  2. * Copyright 2015 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.luoqiz.oauth2.justauth;
  17. import org.springframework.security.authentication.AbstractAuthenticationToken;
  18. import org.springframework.security.core.GrantedAuthority;
  19. import java.io.Serializable;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.Map;
  24. /**
  25. * 自定义认证token
  26. */
  27. public class JustauthAuthenticationToken extends AbstractAuthenticationToken {
  28. private final String providerId;
  29. private final Serializable principle;
  30. private final Map<String, String> providerAccountData;
  31. /**
  32. * @param providerAccountData optional extra account data
  33. * @param authorities
  34. */
  35. public JustauthAuthenticationToken(String providerId, Serializable principle, Map<String, String> providerAccountData,
  36. Collection<? extends GrantedAuthority> authorities) {
  37. super(authorities);
  38. this.providerId = providerId;
  39. this.principle = principle; //no principal yet
  40. if (providerAccountData != null) {
  41. this.providerAccountData = Collections.unmodifiableMap(new HashMap<String, String>(providerAccountData));
  42. } else {
  43. this.providerAccountData = Collections.emptyMap();
  44. }
  45. super.setAuthenticated(true);
  46. }
  47. /**
  48. * @param authorities any {@link GrantedAuthority}s for this user
  49. */
  50. public JustauthAuthenticationToken(String providerId, Serializable principle, Collection<? extends GrantedAuthority> authorities, Boolean b) {
  51. super(authorities);
  52. this.providerId = providerId;
  53. this.principle = principle;
  54. this.providerAccountData = null;
  55. super.setAuthenticated(b);
  56. }
  57. public String getProviderId() {
  58. return providerId;
  59. }
  60. /**
  61. * @return always null
  62. */
  63. public Object getCredentials() {
  64. return null;
  65. }
  66. @Override
  67. public Object getPrincipal() {
  68. return this.principle;
  69. }
  70. /**
  71. * @return unmodifiable map, never null
  72. */
  73. public Map<String, String> getProviderAccountData() {
  74. return providerAccountData;
  75. }
  76. /**
  77. * @throws IllegalArgumentException when trying to authenticate a previously unauthenticated token
  78. */
  79. @Override
  80. public void setAuthenticated(final boolean isAuthenticated) throws IllegalArgumentException {
  81. if (!isAuthenticated) {
  82. super.setAuthenticated(false);
  83. } else if (!super.isAuthenticated()) {
  84. throw new IllegalArgumentException(
  85. "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
  86. }
  87. }
  88. }

自系统内的认证

  1. package com.luoqiz.oauth2.justauth;
  2. import com.luoqiz.oauth2.justauth.provider.JustauthUserDetailsService;
  3. import com.luoqiz.oauth2.justauth.support.UsersConnectionRepository;
  4. import com.luoqiz.oauth2.justauth.support.jdbc.JdbcUsersConnectionRepository;
  5. import me.zhyd.oauth.model.AuthUser;
  6. import org.springframework.security.authentication.AuthenticationProvider;
  7. import org.springframework.security.authentication.BadCredentialsException;
  8. import org.springframework.security.core.Authentication;
  9. import org.springframework.security.core.AuthenticationException;
  10. import org.springframework.security.core.GrantedAuthority;
  11. import org.springframework.security.core.userdetails.UserDetails;
  12. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  13. import org.springframework.util.Assert;
  14. import java.util.Collection;
  15. import java.util.HashSet;
  16. import java.util.Set;
  17. /**
  18. * 从第三方正确获取用户信息后,自系统用户处理
  19. */
  20. public class JustauthAuthenticationProvider implements AuthenticationProvider {
  21. private UsersConnectionRepository usersConnectionRepository;
  22. private JustauthUserDetailsService userDetailsService;
  23. public JustauthAuthenticationProvider(UsersConnectionRepository usersConnectionRepository, JustauthUserDetailsService userDetailsService) {
  24. this.usersConnectionRepository = usersConnectionRepository;
  25. this.userDetailsService = userDetailsService;
  26. }
  27. public boolean supports(Class<? extends Object> authentication) {
  28. return JustauthAuthenticationToken.class.isAssignableFrom(authentication);
  29. }
  30. /**
  31. * Authenticate user based on {@link JustauthAuthenticationToken}
  32. */
  33. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  34. Assert.isInstanceOf(JustauthAuthenticationToken.class, authentication, "unsupported justAuth authentication type");
  35. Assert.isTrue(!authentication.isAuthenticated(), "already authenticated");
  36. JustauthAuthenticationToken authToken = (JustauthAuthenticationToken) authentication;
  37. String providerId = authToken.getProviderId();
  38. AuthUser authUser = (AuthUser) authToken.getPrincipal();
  39. String userId = toUserId(authUser);
  40. if (userId == null) {
  41. throw new BadCredentialsException("Unknown access token");
  42. }
  43. UserDetails userDetails = userDetailsService.loadUserByUserId(userId);
  44. if (userDetails == null) {
  45. throw new UsernameNotFoundException("Unknown connected account id");
  46. }
  47. return new JustauthAuthenticationToken(providerId, userDetails, getAuthorities(providerId, userDetails), true);
  48. }
  49. protected String toUserId(AuthUser authUser) {
  50. Set<String> providerUserIds = new HashSet<>(1);
  51. providerUserIds.add(authUser.getUuid());
  52. Set<String> userIds = usersConnectionRepository.findUserIdsConnectedTo(authUser.getSource(), providerUserIds);
  53. // only if a single userId is connected to this providerUserId
  54. return (userIds.size() == 1) ? userIds.iterator().next() : null;
  55. }
  56. protected Collection<? extends GrantedAuthority> getAuthorities(String providerId, UserDetails userDetails) {
  57. return userDetails.getAuthorities();
  58. }
  59. }

创建认证过滤器

默认拦截路径为:/justauth/{providerId}

  1. package com.luoqiz.oauth2.justauth;
  2. import com.luoqiz.oauth2.justauth.provider.JustauthUserDetailsService;
  3. import com.luoqiz.oauth2.justauth.support.UsersConnectionRepository;
  4. import com.luoqiz.oauth2.justauth.support.jdbc.JdbcUsersConnectionRepository;
  5. import me.zhyd.oauth.model.AuthUser;
  6. import org.springframework.security.authentication.AuthenticationProvider;
  7. import org.springframework.security.authentication.BadCredentialsException;
  8. import org.springframework.security.core.Authentication;
  9. import org.springframework.security.core.AuthenticationException;
  10. import org.springframework.security.core.GrantedAuthority;
  11. import org.springframework.security.core.userdetails.UserDetails;
  12. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  13. import org.springframework.util.Assert;
  14. import java.util.Collection;
  15. import java.util.HashSet;
  16. import java.util.Set;
  17. /**
  18. * 从第三方正确获取用户信息后,自系统用户处理
  19. */
  20. public class JustauthAuthenticationProvider implements AuthenticationProvider {
  21. private UsersConnectionRepository usersConnectionRepository;
  22. private JustauthUserDetailsService userDetailsService;
  23. public JustauthAuthenticationProvider(UsersConnectionRepository usersConnectionRepository, JustauthUserDetailsService userDetailsService) {
  24. this.usersConnectionRepository = usersConnectionRepository;
  25. this.userDetailsService = userDetailsService;
  26. }
  27. public boolean supports(Class<? extends Object> authentication) {
  28. return JustauthAuthenticationToken.class.isAssignableFrom(authentication);
  29. }
  30. /**
  31. * Authenticate user based on {@link JustauthAuthenticationToken}
  32. */
  33. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  34. Assert.isInstanceOf(JustauthAuthenticationToken.class, authentication, "unsupported justAuth authentication type");
  35. Assert.isTrue(!authentication.isAuthenticated(), "already authenticated");
  36. JustauthAuthenticationToken authToken = (JustauthAuthenticationToken) authentication;
  37. String providerId = authToken.getProviderId();
  38. AuthUser authUser = (AuthUser) authToken.getPrincipal();
  39. String userId = toUserId(authUser);
  40. if (userId == null) {
  41. throw new BadCredentialsException("Unknown access token");
  42. }
  43. UserDetails userDetails = userDetailsService.loadUserByUserId(userId);
  44. if (userDetails == null) {
  45. throw new UsernameNotFoundException("Unknown connected account id");
  46. }
  47. return new JustauthAuthenticationToken(providerId, userDetails, getAuthorities(providerId, userDetails), true);
  48. }
  49. protected String toUserId(AuthUser authUser) {
  50. Set<String> providerUserIds = new HashSet<>(1);
  51. providerUserIds.add(authUser.getUuid());
  52. Set<String> userIds = usersConnectionRepository.findUserIdsConnectedTo(authUser.getSource(), providerUserIds);
  53. // only if a single userId is connected to this providerUserId
  54. return (userIds.size() == 1) ? userIds.iterator().next() : null;
  55. }
  56. protected Collection<? extends GrantedAuthority> getAuthorities(String providerId, UserDetails userDetails) {
  57. return userDetails.getAuthorities();
  58. }
  59. }

系统配置过滤器

  1. /*
  2. * Copyright 2015 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.luoqiz.oauth2.justauth;
  17. import com.luoqiz.oauth2.justauth.connect.JustauthAuthenticationServiceRegistry;
  18. import com.luoqiz.oauth2.justauth.provider.JustauthUserDetailsService;
  19. import com.luoqiz.oauth2.justauth.service.GiteeJustauthAuthenticationService;
  20. import com.luoqiz.oauth2.justauth.service.GithubJustauthAuthenticationService;
  21. import com.luoqiz.oauth2.justauth.service.GoogleJustauthAuthenticationService;
  22. import com.luoqiz.oauth2.justauth.support.UsersConnectionRepository;
  23. import com.luoqiz.oauth2.justauth.support.jdbc.JdbcUsersConnectionRepository;
  24. import com.xkcoding.justauth.AuthRequestFactory;
  25. import lombok.Data;
  26. import me.zhyd.oauth.model.AuthUser;
  27. import org.springframework.security.authentication.AuthenticationManager;
  28. import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
  29. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  30. import org.springframework.security.web.DefaultSecurityFilterChain;
  31. import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
  32. import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
  33. import org.springframework.security.web.authentication.AuthenticationFailureHandler;
  34. import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
  35. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  36. /**
  37. * 系统配置过滤器
  38. */
  39. @Data
  40. public class JustAuthSocialConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
  41. private AuthRequestFactory factory;
  42. private UsersConnectionRepository jdbcUsersConnectionRepository;
  43. private JustauthUserDetailsService userDetailsService;
  44. private AuthenticationSuccessHandler authenticationSuccessHandler;
  45. private AuthenticationFailureHandler authenticationFailureHandler;
  46. public JustAuthSocialConfigurer(AuthRequestFactory factory, UsersConnectionRepository jdbcUsersConnectionRepository) {
  47. this.factory = factory;
  48. this.jdbcUsersConnectionRepository = jdbcUsersConnectionRepository;
  49. }
  50. /**
  51. * 此组件已经实现的服务
  52. */
  53. private void justauthAuthenticationServiceRegistry() {
  54. JustauthAuthenticationServiceRegistry.getInstance().addJustauthAuthenticationService("github", new GithubJustauthAuthenticationService<>("github", factory));
  55. JustauthAuthenticationServiceRegistry.getInstance().addJustauthAuthenticationService("google", new GoogleJustauthAuthenticationService<>("google", factory));
  56. JustauthAuthenticationServiceRegistry.getInstance().addJustauthAuthenticationService("gitee", new GiteeJustauthAuthenticationService<>("gitee", factory));
  57. }
  58. /**
  59. * 添加自定义 JustauthAuthenticationService 服务
  60. *
  61. * @param key
  62. * @param service
  63. */
  64. public void addJustauthAuthenticationService(String key, AbstractJustauthAuthenticationService<AuthUser> service) {
  65. service.setFactory(factory);
  66. JustauthAuthenticationServiceRegistry.getInstance().addJustauthAuthenticationService(key, service);
  67. }
  68. @Override
  69. public void configure(HttpSecurity http) throws Exception {
  70. justauthAuthenticationServiceRegistry();
  71. JustAuthSocialAuthenticationFilter filter = new JustAuthSocialAuthenticationFilter(
  72. http.getSharedObject(AuthenticationManager.class), factory, jdbcUsersConnectionRepository);
  73. filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
  74. filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
  75. filter.setAuthenticationFailureHandler(authenticationFailureHandler);
  76. http.authenticationProvider(
  77. new JustauthAuthenticationProvider(jdbcUsersConnectionRepository, userDetailsService))
  78. // .addFilterBefore(postProcess(filter), AbstractPreAuthenticatedProcessingFilter.class);
  79. .addFilterBefore(postProcess(filter), AnonymousAuthenticationFilter.class);
  80. }
  81. }

用户来源提供

  1. package com.luoqiz.oauth2.justauth.support.jdbc;
  2. import com.luoqiz.oauth2.justauth.support.ConnectionData;
  3. import com.luoqiz.oauth2.justauth.support.UsersConnectionRepository;
  4. import org.springframework.dao.DataAccessException;
  5. import org.springframework.dao.DuplicateKeyException;
  6. import org.springframework.jdbc.core.BeanPropertyRowMapper;
  7. import org.springframework.jdbc.core.JdbcTemplate;
  8. import org.springframework.jdbc.core.ResultSetExtractor;
  9. import org.springframework.jdbc.core.RowMapper;
  10. import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
  11. import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
  12. import org.springframework.security.crypto.encrypt.Encryptors;
  13. import org.springframework.security.crypto.encrypt.TextEncryptor;
  14. import org.springframework.transaction.annotation.Transactional;
  15. import javax.sql.DataSource;
  16. import java.sql.ResultSet;
  17. import java.sql.SQLException;
  18. import java.util.*;
  19. /**
  20. * 认证来源,目前只实现保存在数据库中的用户
  21. */
  22. public class JdbcUsersConnectionRepository implements UsersConnectionRepository {
  23. private final JdbcTemplate jdbcTemplate;
  24. private final TextEncryptor textEncryptor;
  25. private String tablePrefix = "";
  26. public JdbcUsersConnectionRepository(DataSource dataSource, TextEncryptor textEncryptor) {
  27. this.jdbcTemplate = new JdbcTemplate(dataSource);
  28. if (textEncryptor != null) {
  29. this.textEncryptor = textEncryptor;
  30. } else {
  31. this.textEncryptor = Encryptors.noOpText();
  32. }
  33. }
  34. /**
  35. * Sets a table name prefix. This will be prefixed to all the table names before queries are executed. Defaults to "".
  36. * This is can be used to qualify the table name with a schema or to distinguish Spring Social tables from other application tables.
  37. *
  38. * @param tablePrefix the tablePrefix to set
  39. */
  40. public void setTablePrefix(String tablePrefix) {
  41. this.tablePrefix = tablePrefix;
  42. }
  43. @Override
  44. public List<String> findUserIdsWithProvider(String providerId, String providerUserId) {
  45. List<String> localUserIds = jdbcTemplate.queryForList("select userId from " + tablePrefix + "UserConnection where providerId = ? and providerUserId = ?", String.class, providerId, providerUserId);
  46. // if (localUserIds.size() == 0 ) {
  47. // String newUserId = connectionSignUp.execute(connection);
  48. // if (newUserId != null) {
  49. // createConnectionRepository(newUserId).addConnection(connection);
  50. // return Arrays.asList(newUserId);
  51. // }
  52. // }
  53. return localUserIds;
  54. }
  55. @Override
  56. public Set<String> findUserIdsConnectedTo(String providerId, Set<String> providerUserIds) {
  57. MapSqlParameterSource parameters = new MapSqlParameterSource();
  58. parameters.addValue("providerId", providerId);
  59. parameters.addValue("providerUserIds", providerUserIds);
  60. final Set<String> localUserIds = new HashSet<String>();
  61. return new NamedParameterJdbcTemplate(jdbcTemplate).query("select userId from " + tablePrefix + "UserConnection where providerId = :providerId and providerUserId in (:providerUserIds)", parameters,
  62. new ResultSetExtractor<Set<String>>() {
  63. public Set<String> extractData(ResultSet rs) throws SQLException, DataAccessException {
  64. while (rs.next()) {
  65. localUserIds.add(rs.getString("userId"));
  66. }
  67. return localUserIds;
  68. }
  69. });
  70. }
  71. @Override
  72. public ConnectionData findRowWithUserIdProviderId(String userId, String providerId) {
  73. RowMapper<ConnectionData> rm = new BeanPropertyRowMapper<ConnectionData>(ConnectionData.class);
  74. return jdbcTemplate.queryForObject("select * from " + tablePrefix + "UserConnection where userId = ? and providerId = ?",
  75. new Object[]{userId, providerId}, new RowMapper<ConnectionData>() {
  76. @Override
  77. public ConnectionData mapRow(ResultSet resultSet, int i) throws SQLException {
  78. return new ConnectionData(resultSet.getString("userId"),
  79. resultSet.getString("providerId"),
  80. resultSet.getString("providerUserId"),
  81. resultSet.getString("displayName"),
  82. resultSet.getString("profileUrl"),
  83. resultSet.getString("imageUrl"),
  84. resultSet.getString("accessToken"),
  85. resultSet.getString("secret"),
  86. resultSet.getString("refreshToken"),
  87. resultSet.getLong("expireTime")
  88. );
  89. }
  90. });
  91. }
  92. @Override
  93. @Transactional
  94. public void addConnection(ConnectionData connectionData) {
  95. try {
  96. int rank = jdbcTemplate.queryForObject("select coalesce(max(rank) + 1, 1) as rank from " + tablePrefix + "UserConnection where userId = ? and providerId = ?", new Object[]{connectionData.getUserId(), connectionData.getProviderId()}, Integer.class);
  97. jdbcTemplate.update("insert into " + tablePrefix + "UserConnection (userId, providerId, providerUserId, rank, displayName, profileUrl, imageUrl, accessToken, secret, refreshToken, expireTime) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
  98. connectionData.getUserId(), connectionData.getProviderId(), connectionData.getProviderUserId(),
  99. rank, connectionData.getDisplayName(), connectionData.getProfileUrl(),
  100. connectionData.getImageUrl(), encrypt(connectionData.getAccessToken()),
  101. encrypt(connectionData.getSecret()), encrypt(connectionData.getRefreshToken()), connectionData.getExpireTime());
  102. } catch (DuplicateKeyException e) {
  103. throw new DuplicateKeyException(connectionData.getUserId());
  104. }
  105. }
  106. @Override
  107. @Transactional
  108. public void updateConnection(ConnectionData data) {
  109. jdbcTemplate.update("update " + tablePrefix + "UserConnection set displayName = ?, profileUrl = ?, imageUrl = ?, accessToken = ?, secret = ?, refreshToken = ?, expireTime = ? where userId = ? and providerId = ? and providerUserId = ?",
  110. data.getDisplayName(), data.getProfileUrl(), data.getImageUrl(), encrypt(data.getAccessToken()),
  111. encrypt(data.getSecret()), encrypt(data.getRefreshToken()), data.getExpireTime(),
  112. data.getUserId(), data.getProviderId(), data.getProviderUserId());
  113. }
  114. @Override
  115. @Transactional
  116. public boolean removeConnections(String providerId, String userId) {
  117. return jdbcTemplate.update("delete from " + tablePrefix + "UserConnection where userId = ? and providerId = ?",
  118. userId, providerId) == 1;
  119. }
  120. // @Transactional
  121. // public void removeConnection(ConnectionKey connectionKey) {
  122. // jdbcTemplate.update("delete from " + tablePrefix + "UserConnection where userId = ? and providerId = ? and providerUserId = ?", userId, connectionKey.getProviderId(), connectionKey.getProviderUserId());
  123. // }
  124. // internal helpers
  125. private String selectFromUserConnection() {
  126. return "select userId, providerId, providerUserId, displayName, profileUrl, imageUrl, accessToken, secret, refreshToken, expireTime from " + tablePrefix + "UserConnection";
  127. }
  128. // private Connection<?> findPrimaryConnection(String providerId) {
  129. // List<Connection<?>> connections = jdbcTemplate.query(selectFromUserConnection() + " where userId = ? and providerId = ? order by rank", connectionMapper, userId, providerId);
  130. // if (connections.size() > 0) {
  131. // return connections.get(0);
  132. // } else {
  133. // return null;
  134. // }
  135. // }
  136. private String encrypt(String text) {
  137. return text != null ? textEncryptor.encrypt(text) : text;
  138. }
  139. @Override
  140. public List<ConnectionData> findUserWithUserId(String userId) {
  141. List<Map<String, Object>> result = jdbcTemplate.queryForList("select * from " + tablePrefix + "UserConnection where userId = ? ",
  142. new Object[]{userId});
  143. List<ConnectionData> rs = new ArrayList<>();
  144. result.forEach(resultSet -> {
  145. ConnectionData connection = new ConnectionData(
  146. (String) resultSet.get("userId"),
  147. (String) resultSet.get("providerId"),
  148. (String) resultSet.get("providerUserId"),
  149. (String) resultSet.get("displayName"),
  150. (String) resultSet.get("profileUrl"),
  151. (String) resultSet.get("imageUrl"),
  152. (String) resultSet.get("accessToken"),
  153. (String) resultSet.get("secret"),
  154. (String) resultSet.get("refreshToken"),
  155. (Long) resultSet.get("expireTime")
  156. );
  157. rs.add(connection);
  158. });
  159. return rs;
  160. }
  161. }

使用配置

认证失败处理器

  1. package com.luoqiz.pincheng.config.permission;
  2. import cn.hutool.json.JSONObject;
  3. import com.luoqiz.oauth2.justauth.exception.JustauthAuthenticationRedirectException;
  4. import com.luoqiz.pincheng.config.exception.BusinessExCodeEnum;
  5. import com.luoqiz.pincheng.config.exception.BusinessException;
  6. import com.luoqiz.pincheng.util.Result;
  7. import org.springframework.security.core.AuthenticationException;
  8. import org.springframework.security.web.authentication.AuthenticationFailureHandler;
  9. import org.springframework.stereotype.Component;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.IOException;
  13. /**
  14. * 用户登录认证失败返回的信息
  15. */
  16. @Component
  17. public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {
  18. @Override
  19. public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
  20. if (exception instanceof JustauthAuthenticationRedirectException) {
  21. response.sendRedirect(((JustauthAuthenticationRedirectException) exception).getRedirectUrl());
  22. return;
  23. }
  24. response.setContentType("application/json;charset=UTF-8");
  25. Result result = Result.error(new BusinessException(BusinessExCodeEnum.LOGIN_ERROR));
  26. JSONObject json = new JSONObject(result);
  27. response.getWriter().write(json.toJSONString(0));
  28. }
  29. }

注意: if (exception instanceof JustauthAuthenticationRedirectException) 的判断必须有,否则在需要跳转到第三方登录的时候会出现失败的情况。

认证相关配置

  1. package com.luoqiz.pincheng.config;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import com.luoqiz.oauth2.justauth.JustAuthSocialConfigurer;
  4. import com.luoqiz.oauth2.justauth.support.UsersConnectionRepository;
  5. import com.luoqiz.oauth2.justauth.support.jdbc.JdbcUsersConnectionRepository;
  6. import com.luoqiz.pincheng.config.permission.JwtAuthenticationTokenFilter;
  7. import com.luoqiz.pincheng.justauth.WjwJustauthAuthenticationService;
  8. import com.luoqiz.pincheng.service.ISysUserService;
  9. import com.xkcoding.justauth.AuthRequestFactory;
  10. import lombok.extern.slf4j.Slf4j;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.context.annotation.Bean;
  13. import org.springframework.context.annotation.Configuration;
  14. import org.springframework.context.annotation.Primary;
  15. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  16. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  17. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  18. import org.springframework.security.config.http.SessionCreationPolicy;
  19. import org.springframework.security.crypto.encrypt.TextEncryptor;
  20. import org.springframework.security.crypto.factory.PasswordEncoderFactories;
  21. import org.springframework.security.crypto.password.PasswordEncoder;
  22. import org.springframework.security.web.AuthenticationEntryPoint;
  23. import org.springframework.security.web.access.AccessDeniedHandler;
  24. import org.springframework.security.web.authentication.AuthenticationFailureHandler;
  25. import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
  26. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  27. import org.springframework.transaction.annotation.EnableTransactionManagement;
  28. import javax.sql.DataSource;
  29. /**
  30. * @author luoqiz
  31. * @date 2019/7/21
  32. * 认证相关配置
  33. */
  34. @Slf4j
  35. @Primary
  36. @Configuration
  37. @EnableTransactionManagement(proxyTargetClass = true)
  38. public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
  39. @Autowired
  40. private ObjectMapper objectMapper;
  41. @Autowired
  42. private ISysUserService sysUserService;
  43. @Autowired
  44. private AuthenticationEntryPoint authenticationEntryPoint;
  45. @Autowired
  46. private AuthenticationSuccessHandler authenticationSuccessHandler;
  47. @Autowired
  48. private AuthenticationFailureHandler authenticationFailureHandler;
  49. @Autowired
  50. private AccessDeniedHandler accessDeniedHandler;
  51. @Autowired
  52. JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器
  53. @Autowired
  54. AuthRequestFactory factory;
  55. @Autowired
  56. DataSource dataSource;
  57. @Autowired(required = false)
  58. private TextEncryptor encryptor;
  59. @Bean
  60. protected UsersConnectionRepository jdbcUsersConnectionRepository() {
  61. return new JdbcUsersConnectionRepository(dataSource, encryptor);
  62. }
  63. @Override
  64. protected void configure(HttpSecurity http) throws Exception {
  65. http
  66. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  67. .and()
  68. .httpBasic().authenticationEntryPoint(authenticationEntryPoint)
  69. .and()
  70. .formLogin()
  71. // 登录表单提交地址
  72. .loginProcessingUrl("/login")
  73. .successHandler(authenticationSuccessHandler)
  74. .failureHandler(authenticationFailureHandler)
  75. .permitAll()
  76. .and()
  77. .authorizeRequests()
  78. .antMatchers("/admin/**").authenticated()
  79. .anyRequest().permitAll()
  80. .and()
  81. .cors().and()
  82. .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
  83. .and().csrf().disable();
  84. JustAuthSocialConfigurer justauth = new JustAuthSocialConfigurer(factory, jdbcUsersConnectionRepository());
  85. justauth.setUserDetailsService(sysUserService);
  86. justauth.setAuthenticationFailureHandler(authenticationFailureHandler);
  87. justauth.setAuthenticationSuccessHandler(authenticationSuccessHandler);
  88. justauth.addJustauthAuthenticationService("wjw", new WjwJustauthAuthenticationService("wjw", factory));
  89. http.apply(justauth);
  90. http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter
  91. }
  92. @Override
  93. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  94. auth.userDetailsService(sysUserService).passwordEncoder(passwordEncoder());
  95. }
  96. @Bean
  97. public PasswordEncoder passwordEncoder() {
  98. return PasswordEncoderFactories.createDelegatingPasswordEncoder();
  99. }
  100. }

第三方信息配置

  1. justauth:
  2. enabled: true
  3. type:
  4. github:
  5. client-id: 323a5*********297b
  6. client-secret: 380************32f33
  7. redirect-uri: http://********/justauth/github
  8. google:
  9. client-id: 15330************.apps.googleusercontent.com
  10. client-secret: gCzAl*************9sH
  11. redirect-uri: http://********/justauth/google
  12. gitee:
  13. client-id: 12f1d90************431db5c65d850d
  14. client-secret: ac29e60632*************be25cfc6d6e5ae2811e
  15. redirect-uri: http://********/justauth/gitee
  16. extend:
  17. enumClass: com.luoqiz.test.justauth.AuthCustomSource
  18. config:
  19. wjw:
  20. request-class: com.luoqiz.test.justauth.AuthWjwRequest
  21. client-id: Y***********A
  22. client-secret: d00955f*************3005fcd109
  23. redirect-uri: http://test.luoqiz.top/justauth/wjw

配置完成即可使用。

项目地址:https://gitee.com/luoqiz/justauth-spring-security-starter.git
此为自己写的为自己使用。可参考原理编写更适合自己的程序,如临时用户信息存储等。若是对此项目有好的建议或者需求可提issues。优化代码后会上传到 maven 仓库,方便使用和查看。

演示

springsecurity-justauth演示.wmv (4.18MB)