介绍
原理简介
spring security基本原理:通过使用过滤器实现拦截数据流,并对参数进行一系列的处理。 最终由FilterSecurityInterceptor类拦截,并对之前过滤器的异常做处理(具体可参考:https://blog.csdn.net/duanshengjie/article/details/79985196)。
此项目也是开发了一个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 {
private String providerId;private AuthRequestFactory factory;public AbstractJustauthAuthenticationService(String providerId, AuthRequestFactory factory) {this.providerId = providerId;this.factory = factory;}public void afterPropertiesSet(AuthUser user) throws Exception {}//此处跳转到第三方认证@Overridepublic JustauthAuthenticationToken getAuthToken(HttpServletRequest request, HttpServletResponse response) {//判断是否是第一次登录String code = request.getParameter("code");if (!StringUtils.hasText(code)) {AuthRequest authRequest = factory.get(providerId);String state = providerId + "::" + AuthStateUtils.createState();Authentication auth = SecurityContextHolder.getContext().getAuthentication();if (auth != null) {log.info("auth user : {}", auth.toString());ConcurrentHashMapCacheUtils.setCache(state, auth, 120 * 1000);}throw new JustauthAuthenticationRedirectException(authRequest.authorize(state));} else if (StringUtils.hasText(code)) {try {AuthRequest authRequest = factory.get(providerId);AuthCallback callback = new AuthCallback();callback.setCode(code);String state = request.getParameter("state");callback.setState(state);Authentication auth = (Authentication) ConcurrentHashMapCacheUtils.getCache(state);if (auth != null) {log.info("auth user ---- : {}", auth.toString());SecurityContextHolder.getContext().setAuthentication(auth);}AuthUser user = parseUser(authRequest.login(callback));afterPropertiesSet(user);return new JustauthAuthenticationToken(providerId, user, null, false);} catch (Exception e) {log.debug("failed to exchange for access", e);return null;}} else {return null;}}protected AuthUser parseUser(AuthResponse<S> response) {if (response.getCode() == 2000) {AuthUser authuser = response.getData();return authuser;}return null;}
}
<br />注意:此时需要跳转到第三方去登录认证的时候,若是已经登录,则会临时保存用户信息。是使用系统内自定义的线程。```javapackage com.luoqiz.oauth2.justauth.service;import com.luoqiz.oauth2.justauth.AbstractJustauthAuthenticationService;import com.xkcoding.justauth.AuthRequestFactory;import lombok.extern.slf4j.Slf4j;import me.zhyd.oauth.model.AuthUser;@Slf4jpublic class GithubJustauthAuthenticationService<A> extends AbstractJustauthAuthenticationService<AuthUser> {public GithubJustauthAuthenticationService(String providerId, AuthRequestFactory factory) {super(providerId,factory );}}
认证token
/** Copyright 2015 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.luoqiz.oauth2.justauth;import org.springframework.security.authentication.AbstractAuthenticationToken;import org.springframework.security.core.GrantedAuthority;import java.io.Serializable;import java.util.Collection;import java.util.Collections;import java.util.HashMap;import java.util.Map;/*** 自定义认证token*/public class JustauthAuthenticationToken extends AbstractAuthenticationToken {private final String providerId;private final Serializable principle;private final Map<String, String> providerAccountData;/*** @param providerAccountData optional extra account data* @param authorities*/public JustauthAuthenticationToken(String providerId, Serializable principle, Map<String, String> providerAccountData,Collection<? extends GrantedAuthority> authorities) {super(authorities);this.providerId = providerId;this.principle = principle; //no principal yetif (providerAccountData != null) {this.providerAccountData = Collections.unmodifiableMap(new HashMap<String, String>(providerAccountData));} else {this.providerAccountData = Collections.emptyMap();}super.setAuthenticated(true);}/*** @param authorities any {@link GrantedAuthority}s for this user*/public JustauthAuthenticationToken(String providerId, Serializable principle, Collection<? extends GrantedAuthority> authorities, Boolean b) {super(authorities);this.providerId = providerId;this.principle = principle;this.providerAccountData = null;super.setAuthenticated(b);}public String getProviderId() {return providerId;}/*** @return always null*/public Object getCredentials() {return null;}@Overridepublic Object getPrincipal() {return this.principle;}/*** @return unmodifiable map, never null*/public Map<String, String> getProviderAccountData() {return providerAccountData;}/*** @throws IllegalArgumentException when trying to authenticate a previously unauthenticated token*/@Overridepublic void setAuthenticated(final boolean isAuthenticated) throws IllegalArgumentException {if (!isAuthenticated) {super.setAuthenticated(false);} else if (!super.isAuthenticated()) {throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");}}}
自系统内的认证
package com.luoqiz.oauth2.justauth;import com.luoqiz.oauth2.justauth.provider.JustauthUserDetailsService;import com.luoqiz.oauth2.justauth.support.UsersConnectionRepository;import com.luoqiz.oauth2.justauth.support.jdbc.JdbcUsersConnectionRepository;import me.zhyd.oauth.model.AuthUser;import org.springframework.security.authentication.AuthenticationProvider;import org.springframework.security.authentication.BadCredentialsException;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.util.Assert;import java.util.Collection;import java.util.HashSet;import java.util.Set;/*** 从第三方正确获取用户信息后,自系统用户处理*/public class JustauthAuthenticationProvider implements AuthenticationProvider {private UsersConnectionRepository usersConnectionRepository;private JustauthUserDetailsService userDetailsService;public JustauthAuthenticationProvider(UsersConnectionRepository usersConnectionRepository, JustauthUserDetailsService userDetailsService) {this.usersConnectionRepository = usersConnectionRepository;this.userDetailsService = userDetailsService;}public boolean supports(Class<? extends Object> authentication) {return JustauthAuthenticationToken.class.isAssignableFrom(authentication);}/*** Authenticate user based on {@link JustauthAuthenticationToken}*/public Authentication authenticate(Authentication authentication) throws AuthenticationException {Assert.isInstanceOf(JustauthAuthenticationToken.class, authentication, "unsupported justAuth authentication type");Assert.isTrue(!authentication.isAuthenticated(), "already authenticated");JustauthAuthenticationToken authToken = (JustauthAuthenticationToken) authentication;String providerId = authToken.getProviderId();AuthUser authUser = (AuthUser) authToken.getPrincipal();String userId = toUserId(authUser);if (userId == null) {throw new BadCredentialsException("Unknown access token");}UserDetails userDetails = userDetailsService.loadUserByUserId(userId);if (userDetails == null) {throw new UsernameNotFoundException("Unknown connected account id");}return new JustauthAuthenticationToken(providerId, userDetails, getAuthorities(providerId, userDetails), true);}protected String toUserId(AuthUser authUser) {Set<String> providerUserIds = new HashSet<>(1);providerUserIds.add(authUser.getUuid());Set<String> userIds = usersConnectionRepository.findUserIdsConnectedTo(authUser.getSource(), providerUserIds);// only if a single userId is connected to this providerUserIdreturn (userIds.size() == 1) ? userIds.iterator().next() : null;}protected Collection<? extends GrantedAuthority> getAuthorities(String providerId, UserDetails userDetails) {return userDetails.getAuthorities();}}
创建认证过滤器
默认拦截路径为:/justauth/{providerId}
package com.luoqiz.oauth2.justauth;import com.luoqiz.oauth2.justauth.provider.JustauthUserDetailsService;import com.luoqiz.oauth2.justauth.support.UsersConnectionRepository;import com.luoqiz.oauth2.justauth.support.jdbc.JdbcUsersConnectionRepository;import me.zhyd.oauth.model.AuthUser;import org.springframework.security.authentication.AuthenticationProvider;import org.springframework.security.authentication.BadCredentialsException;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.util.Assert;import java.util.Collection;import java.util.HashSet;import java.util.Set;/*** 从第三方正确获取用户信息后,自系统用户处理*/public class JustauthAuthenticationProvider implements AuthenticationProvider {private UsersConnectionRepository usersConnectionRepository;private JustauthUserDetailsService userDetailsService;public JustauthAuthenticationProvider(UsersConnectionRepository usersConnectionRepository, JustauthUserDetailsService userDetailsService) {this.usersConnectionRepository = usersConnectionRepository;this.userDetailsService = userDetailsService;}public boolean supports(Class<? extends Object> authentication) {return JustauthAuthenticationToken.class.isAssignableFrom(authentication);}/*** Authenticate user based on {@link JustauthAuthenticationToken}*/public Authentication authenticate(Authentication authentication) throws AuthenticationException {Assert.isInstanceOf(JustauthAuthenticationToken.class, authentication, "unsupported justAuth authentication type");Assert.isTrue(!authentication.isAuthenticated(), "already authenticated");JustauthAuthenticationToken authToken = (JustauthAuthenticationToken) authentication;String providerId = authToken.getProviderId();AuthUser authUser = (AuthUser) authToken.getPrincipal();String userId = toUserId(authUser);if (userId == null) {throw new BadCredentialsException("Unknown access token");}UserDetails userDetails = userDetailsService.loadUserByUserId(userId);if (userDetails == null) {throw new UsernameNotFoundException("Unknown connected account id");}return new JustauthAuthenticationToken(providerId, userDetails, getAuthorities(providerId, userDetails), true);}protected String toUserId(AuthUser authUser) {Set<String> providerUserIds = new HashSet<>(1);providerUserIds.add(authUser.getUuid());Set<String> userIds = usersConnectionRepository.findUserIdsConnectedTo(authUser.getSource(), providerUserIds);// only if a single userId is connected to this providerUserIdreturn (userIds.size() == 1) ? userIds.iterator().next() : null;}protected Collection<? extends GrantedAuthority> getAuthorities(String providerId, UserDetails userDetails) {return userDetails.getAuthorities();}}
系统配置过滤器
/** Copyright 2015 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.luoqiz.oauth2.justauth;import com.luoqiz.oauth2.justauth.connect.JustauthAuthenticationServiceRegistry;import com.luoqiz.oauth2.justauth.provider.JustauthUserDetailsService;import com.luoqiz.oauth2.justauth.service.GiteeJustauthAuthenticationService;import com.luoqiz.oauth2.justauth.service.GithubJustauthAuthenticationService;import com.luoqiz.oauth2.justauth.service.GoogleJustauthAuthenticationService;import com.luoqiz.oauth2.justauth.support.UsersConnectionRepository;import com.luoqiz.oauth2.justauth.support.jdbc.JdbcUsersConnectionRepository;import com.xkcoding.justauth.AuthRequestFactory;import lombok.Data;import me.zhyd.oauth.model.AuthUser;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.SecurityConfigurerAdapter;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.web.DefaultSecurityFilterChain;import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;import org.springframework.security.web.authentication.AuthenticationFailureHandler;import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;/*** 系统配置过滤器*/@Datapublic class JustAuthSocialConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {private AuthRequestFactory factory;private UsersConnectionRepository jdbcUsersConnectionRepository;private JustauthUserDetailsService userDetailsService;private AuthenticationSuccessHandler authenticationSuccessHandler;private AuthenticationFailureHandler authenticationFailureHandler;public JustAuthSocialConfigurer(AuthRequestFactory factory, UsersConnectionRepository jdbcUsersConnectionRepository) {this.factory = factory;this.jdbcUsersConnectionRepository = jdbcUsersConnectionRepository;}/*** 此组件已经实现的服务*/private void justauthAuthenticationServiceRegistry() {JustauthAuthenticationServiceRegistry.getInstance().addJustauthAuthenticationService("github", new GithubJustauthAuthenticationService<>("github", factory));JustauthAuthenticationServiceRegistry.getInstance().addJustauthAuthenticationService("google", new GoogleJustauthAuthenticationService<>("google", factory));JustauthAuthenticationServiceRegistry.getInstance().addJustauthAuthenticationService("gitee", new GiteeJustauthAuthenticationService<>("gitee", factory));}/*** 添加自定义 JustauthAuthenticationService 服务** @param key* @param service*/public void addJustauthAuthenticationService(String key, AbstractJustauthAuthenticationService<AuthUser> service) {service.setFactory(factory);JustauthAuthenticationServiceRegistry.getInstance().addJustauthAuthenticationService(key, service);}@Overridepublic void configure(HttpSecurity http) throws Exception {justauthAuthenticationServiceRegistry();JustAuthSocialAuthenticationFilter filter = new JustAuthSocialAuthenticationFilter(http.getSharedObject(AuthenticationManager.class), factory, jdbcUsersConnectionRepository);filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);filter.setAuthenticationFailureHandler(authenticationFailureHandler);http.authenticationProvider(new JustauthAuthenticationProvider(jdbcUsersConnectionRepository, userDetailsService))// .addFilterBefore(postProcess(filter), AbstractPreAuthenticatedProcessingFilter.class);.addFilterBefore(postProcess(filter), AnonymousAuthenticationFilter.class);}}
用户来源提供
package com.luoqiz.oauth2.justauth.support.jdbc;import com.luoqiz.oauth2.justauth.support.ConnectionData;import com.luoqiz.oauth2.justauth.support.UsersConnectionRepository;import org.springframework.dao.DataAccessException;import org.springframework.dao.DuplicateKeyException;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.ResultSetExtractor;import org.springframework.jdbc.core.RowMapper;import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;import org.springframework.security.crypto.encrypt.Encryptors;import org.springframework.security.crypto.encrypt.TextEncryptor;import org.springframework.transaction.annotation.Transactional;import javax.sql.DataSource;import java.sql.ResultSet;import java.sql.SQLException;import java.util.*;/*** 认证来源,目前只实现保存在数据库中的用户*/public class JdbcUsersConnectionRepository implements UsersConnectionRepository {private final JdbcTemplate jdbcTemplate;private final TextEncryptor textEncryptor;private String tablePrefix = "";public JdbcUsersConnectionRepository(DataSource dataSource, TextEncryptor textEncryptor) {this.jdbcTemplate = new JdbcTemplate(dataSource);if (textEncryptor != null) {this.textEncryptor = textEncryptor;} else {this.textEncryptor = Encryptors.noOpText();}}/*** Sets a table name prefix. This will be prefixed to all the table names before queries are executed. Defaults to "".* This is can be used to qualify the table name with a schema or to distinguish Spring Social tables from other application tables.** @param tablePrefix the tablePrefix to set*/public void setTablePrefix(String tablePrefix) {this.tablePrefix = tablePrefix;}@Overridepublic List<String> findUserIdsWithProvider(String providerId, String providerUserId) {List<String> localUserIds = jdbcTemplate.queryForList("select userId from " + tablePrefix + "UserConnection where providerId = ? and providerUserId = ?", String.class, providerId, providerUserId);// if (localUserIds.size() == 0 ) {// String newUserId = connectionSignUp.execute(connection);// if (newUserId != null) {// createConnectionRepository(newUserId).addConnection(connection);// return Arrays.asList(newUserId);// }// }return localUserIds;}@Overridepublic Set<String> findUserIdsConnectedTo(String providerId, Set<String> providerUserIds) {MapSqlParameterSource parameters = new MapSqlParameterSource();parameters.addValue("providerId", providerId);parameters.addValue("providerUserIds", providerUserIds);final Set<String> localUserIds = new HashSet<String>();return new NamedParameterJdbcTemplate(jdbcTemplate).query("select userId from " + tablePrefix + "UserConnection where providerId = :providerId and providerUserId in (:providerUserIds)", parameters,new ResultSetExtractor<Set<String>>() {public Set<String> extractData(ResultSet rs) throws SQLException, DataAccessException {while (rs.next()) {localUserIds.add(rs.getString("userId"));}return localUserIds;}});}@Overridepublic ConnectionData findRowWithUserIdProviderId(String userId, String providerId) {RowMapper<ConnectionData> rm = new BeanPropertyRowMapper<ConnectionData>(ConnectionData.class);return jdbcTemplate.queryForObject("select * from " + tablePrefix + "UserConnection where userId = ? and providerId = ?",new Object[]{userId, providerId}, new RowMapper<ConnectionData>() {@Overridepublic ConnectionData mapRow(ResultSet resultSet, int i) throws SQLException {return new ConnectionData(resultSet.getString("userId"),resultSet.getString("providerId"),resultSet.getString("providerUserId"),resultSet.getString("displayName"),resultSet.getString("profileUrl"),resultSet.getString("imageUrl"),resultSet.getString("accessToken"),resultSet.getString("secret"),resultSet.getString("refreshToken"),resultSet.getLong("expireTime"));}});}@Override@Transactionalpublic void addConnection(ConnectionData connectionData) {try {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);jdbcTemplate.update("insert into " + tablePrefix + "UserConnection (userId, providerId, providerUserId, rank, displayName, profileUrl, imageUrl, accessToken, secret, refreshToken, expireTime) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",connectionData.getUserId(), connectionData.getProviderId(), connectionData.getProviderUserId(),rank, connectionData.getDisplayName(), connectionData.getProfileUrl(),connectionData.getImageUrl(), encrypt(connectionData.getAccessToken()),encrypt(connectionData.getSecret()), encrypt(connectionData.getRefreshToken()), connectionData.getExpireTime());} catch (DuplicateKeyException e) {throw new DuplicateKeyException(connectionData.getUserId());}}@Override@Transactionalpublic void updateConnection(ConnectionData data) {jdbcTemplate.update("update " + tablePrefix + "UserConnection set displayName = ?, profileUrl = ?, imageUrl = ?, accessToken = ?, secret = ?, refreshToken = ?, expireTime = ? where userId = ? and providerId = ? and providerUserId = ?",data.getDisplayName(), data.getProfileUrl(), data.getImageUrl(), encrypt(data.getAccessToken()),encrypt(data.getSecret()), encrypt(data.getRefreshToken()), data.getExpireTime(),data.getUserId(), data.getProviderId(), data.getProviderUserId());}@Override@Transactionalpublic boolean removeConnections(String providerId, String userId) {return jdbcTemplate.update("delete from " + tablePrefix + "UserConnection where userId = ? and providerId = ?",userId, providerId) == 1;}// @Transactional// public void removeConnection(ConnectionKey connectionKey) {// jdbcTemplate.update("delete from " + tablePrefix + "UserConnection where userId = ? and providerId = ? and providerUserId = ?", userId, connectionKey.getProviderId(), connectionKey.getProviderUserId());// }// internal helpersprivate String selectFromUserConnection() {return "select userId, providerId, providerUserId, displayName, profileUrl, imageUrl, accessToken, secret, refreshToken, expireTime from " + tablePrefix + "UserConnection";}// private Connection<?> findPrimaryConnection(String providerId) {// List<Connection<?>> connections = jdbcTemplate.query(selectFromUserConnection() + " where userId = ? and providerId = ? order by rank", connectionMapper, userId, providerId);// if (connections.size() > 0) {// return connections.get(0);// } else {// return null;// }// }private String encrypt(String text) {return text != null ? textEncryptor.encrypt(text) : text;}@Overridepublic List<ConnectionData> findUserWithUserId(String userId) {List<Map<String, Object>> result = jdbcTemplate.queryForList("select * from " + tablePrefix + "UserConnection where userId = ? ",new Object[]{userId});List<ConnectionData> rs = new ArrayList<>();result.forEach(resultSet -> {ConnectionData connection = new ConnectionData((String) resultSet.get("userId"),(String) resultSet.get("providerId"),(String) resultSet.get("providerUserId"),(String) resultSet.get("displayName"),(String) resultSet.get("profileUrl"),(String) resultSet.get("imageUrl"),(String) resultSet.get("accessToken"),(String) resultSet.get("secret"),(String) resultSet.get("refreshToken"),(Long) resultSet.get("expireTime"));rs.add(connection);});return rs;}}
使用配置
认证失败处理器
package com.luoqiz.pincheng.config.permission;import cn.hutool.json.JSONObject;import com.luoqiz.oauth2.justauth.exception.JustauthAuthenticationRedirectException;import com.luoqiz.pincheng.config.exception.BusinessExCodeEnum;import com.luoqiz.pincheng.config.exception.BusinessException;import com.luoqiz.pincheng.util.Result;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.AuthenticationFailureHandler;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/*** 用户登录认证失败返回的信息*/@Componentpublic class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {if (exception instanceof JustauthAuthenticationRedirectException) {response.sendRedirect(((JustauthAuthenticationRedirectException) exception).getRedirectUrl());return;}response.setContentType("application/json;charset=UTF-8");Result result = Result.error(new BusinessException(BusinessExCodeEnum.LOGIN_ERROR));JSONObject json = new JSONObject(result);response.getWriter().write(json.toJSONString(0));}}
注意: if (exception instanceof JustauthAuthenticationRedirectException) 的判断必须有,否则在需要跳转到第三方登录的时候会出现失败的情况。
认证相关配置
package com.luoqiz.pincheng.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.luoqiz.oauth2.justauth.JustAuthSocialConfigurer;import com.luoqiz.oauth2.justauth.support.UsersConnectionRepository;import com.luoqiz.oauth2.justauth.support.jdbc.JdbcUsersConnectionRepository;import com.luoqiz.pincheng.config.permission.JwtAuthenticationTokenFilter;import com.luoqiz.pincheng.justauth.WjwJustauthAuthenticationService;import com.luoqiz.pincheng.service.ISysUserService;import com.xkcoding.justauth.AuthRequestFactory;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.config.http.SessionCreationPolicy;import org.springframework.security.crypto.encrypt.TextEncryptor;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.AuthenticationEntryPoint;import org.springframework.security.web.access.AccessDeniedHandler;import org.springframework.security.web.authentication.AuthenticationFailureHandler;import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;/*** @author luoqiz* @date 2019/7/21* 认证相关配置*/@Slf4j@Primary@Configuration@EnableTransactionManagement(proxyTargetClass = true)public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {@Autowiredprivate ObjectMapper objectMapper;@Autowiredprivate ISysUserService sysUserService;@Autowiredprivate AuthenticationEntryPoint authenticationEntryPoint;@Autowiredprivate AuthenticationSuccessHandler authenticationSuccessHandler;@Autowiredprivate AuthenticationFailureHandler authenticationFailureHandler;@Autowiredprivate AccessDeniedHandler accessDeniedHandler;@AutowiredJwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器@AutowiredAuthRequestFactory factory;@AutowiredDataSource dataSource;@Autowired(required = false)private TextEncryptor encryptor;@Beanprotected UsersConnectionRepository jdbcUsersConnectionRepository() {return new JdbcUsersConnectionRepository(dataSource, encryptor);}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().httpBasic().authenticationEntryPoint(authenticationEntryPoint).and().formLogin()// 登录表单提交地址.loginProcessingUrl("/login").successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler).permitAll().and().authorizeRequests().antMatchers("/admin/**").authenticated().anyRequest().permitAll().and().cors().and().exceptionHandling().accessDeniedHandler(accessDeniedHandler).and().csrf().disable();JustAuthSocialConfigurer justauth = new JustAuthSocialConfigurer(factory, jdbcUsersConnectionRepository());justauth.setUserDetailsService(sysUserService);justauth.setAuthenticationFailureHandler(authenticationFailureHandler);justauth.setAuthenticationSuccessHandler(authenticationSuccessHandler);justauth.addJustauthAuthenticationService("wjw", new WjwJustauthAuthenticationService("wjw", factory));http.apply(justauth);http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(sysUserService).passwordEncoder(passwordEncoder());}@Beanpublic PasswordEncoder passwordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}}
第三方信息配置
justauth:enabled: truetype:github:client-id: 323a5*********297bclient-secret: 380************32f33redirect-uri: http://********/justauth/githubgoogle:client-id: 15330************.apps.googleusercontent.comclient-secret: gCzAl*************9sHredirect-uri: http://********/justauth/googlegitee:client-id: 12f1d90************431db5c65d850dclient-secret: ac29e60632*************be25cfc6d6e5ae2811eredirect-uri: http://********/justauth/giteeextend:enumClass: com.luoqiz.test.justauth.AuthCustomSourceconfig:wjw:request-class: com.luoqiz.test.justauth.AuthWjwRequestclient-id: Y***********Aclient-secret: d00955f*************3005fcd109redirect-uri: http://test.luoqiz.top/justauth/wjw
配置完成即可使用。
项目地址:https://gitee.com/luoqiz/justauth-spring-security-starter.git
此为自己写的为自己使用。可参考原理编写更适合自己的程序,如临时用户信息存储等。若是对此项目有好的建议或者需求可提issues。优化代码后会上传到 maven 仓库,方便使用和查看。

