介绍
原理简介
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 {
}
//此处跳转到第三方认证
@Override
public 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 />注意:此时需要跳转到第三方去登录认证的时候,若是已经登录,则会临时保存用户信息。是使用系统内自定义的线程。
```java
package 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;
@Slf4j
public 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 yet
if (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;
}
@Override
public 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
*/
@Override
public 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 providerUserId
return (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 providerUserId
return (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;
/**
* 系统配置过滤器
*/
@Data
public 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);
}
@Override
public 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;
}
@Override
public 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;
}
@Override
public 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;
}
});
}
@Override
public 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>() {
@Override
public 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
@Transactional
public 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
@Transactional
public 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
@Transactional
public 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 helpers
private 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;
}
@Override
public 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;
/**
* 用户登录认证失败返回的信息
*/
@Component
public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {
@Override
public 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 {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ISysUserService sysUserService;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器
@Autowired
AuthRequestFactory factory;
@Autowired
DataSource dataSource;
@Autowired(required = false)
private TextEncryptor encryptor;
@Bean
protected UsersConnectionRepository jdbcUsersConnectionRepository() {
return new JdbcUsersConnectionRepository(dataSource, encryptor);
}
@Override
protected 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
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(sysUserService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
第三方信息配置
justauth:
enabled: true
type:
github:
client-id: 323a5*********297b
client-secret: 380************32f33
redirect-uri: http://********/justauth/github
google:
client-id: 15330************.apps.googleusercontent.com
client-secret: gCzAl*************9sH
redirect-uri: http://********/justauth/google
gitee:
client-id: 12f1d90************431db5c65d850d
client-secret: ac29e60632*************be25cfc6d6e5ae2811e
redirect-uri: http://********/justauth/gitee
extend:
enumClass: com.luoqiz.test.justauth.AuthCustomSource
config:
wjw:
request-class: com.luoqiz.test.justauth.AuthWjwRequest
client-id: Y***********A
client-secret: d00955f*************3005fcd109
redirect-uri: http://test.luoqiz.top/justauth/wjw
配置完成即可使用。
项目地址:https://gitee.com/luoqiz/justauth-spring-security-starter.git
此为自己写的为自己使用。可参考原理编写更适合自己的程序,如临时用户信息存储等。若是对此项目有好的建议或者需求可提issues。优化代码后会上传到 maven 仓库,方便使用和查看。