一:简介
OAuth2授权模式有四种,其中有标准模式、简化模式等,第三方提供的社交登录采用的授权模式会有不同
- 简化模式(简化模式返回的是openId或者是access_token)
- 标准模式(标准授权模式返回的是code)
简化模式获取token
App通过第三方提供的SDK可以获取到第三方的openId或者是第三方对应的access_token, 但是第三方提供的access_token只能访问第三方的接口,不能访问自己服务的接口。我们要做的就是根据第三方的openId来换取自己服务对应的令牌。实现逻辑和短信验证码的逻辑一样,就是自定义一套认证逻辑。
标准模式获取token
标准模式获取token只需要设置社交认证过滤器SocialAuthenticationFilter认证成功后对应的成功处理器即可
二:简化模式获取token
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-security-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-security-example</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-web</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-core</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-config</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 8080
# Redis数据库索引(默认为0)
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root123
redis:
database: 0
host: localhost
port: 6379
password:
logging:
level:
org.springframework: debug
OpenIdAuthenticationToken
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 1L;
/** 身份 */
private final Object principal;
/** 服务提供商 */
private String providerId;
public OpenIdAuthenticationToken(Object openId, String providerId) {
super(null);
this.principal = openId;
this.providerId = providerId;
setAuthenticated(false);
}
public OpenIdAuthenticationToken(Object openId, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = openId;
super.setAuthenticated(true);
}
@Override
public Object getPrincipal() {
return this.principal;
}
public String getProviderId() {
return this.providerId;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
@Override
public Object getCredentials() {
return null;
}
}
OpenIdAuthenticationFilter
public class OpenIdAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private boolean postOnly = true;
public OpenIdAuthenticationFilter(String loginProcessUrlOpenId) {
super(new AntPathRequestMatcher(loginProcessUrlOpenId, "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
// 封装OpenIdAuthenticationToken
String openId = obtainOpenId(request);
String providerId = obtainProviderId(request);
OpenIdAuthenticationToken authRequest = new OpenIdAuthenticationToken(openId, providerId);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
// 开始认证
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainOpenId(HttpServletRequest request) {
String openId = request.getParameter("openId");
if (openId == null) {
openId = "";
}
openId = openId.trim();
return openId;
}
protected String obtainProviderId(HttpServletRequest request) {
String providerId = request.getParameter("providerId");
providerId = providerId.trim();
return providerId;
}
protected void setDetails(HttpServletRequest request,
OpenIdAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
}
public class OpenIdAuthenticationProvider implements AuthenticationProvider {
private SocialUserDetailsService userDetailsService;
private UsersConnectionRepository usersConnectionRepository;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 从数据库UserConnection表查询数据
OpenIdAuthenticationToken authenticationToken = (OpenIdAuthenticationToken) authentication;
Set<String> providerUserIds = new HashSet<>();
providerUserIds.add((String)authenticationToken.getPrincipal());
Set<String> userIds = usersConnectionRepository.findUserIdsConnectedTo(authenticationToken.getProviderId(), providerUserIds);
if (CollectionUtils.isEmpty(userIds) || userIds.size() != 1) {
throw new InternalAuthenticationServiceException("无法获取用户信息");
}
// 根据用户id查询用户信息
String userId = userIds.iterator().next();
UserDetails user = userDetailsService.loadUserByUserId(userId);
if (user == null) {
throw new InternalAuthenticationServiceException("无法获取用户信息");
}
// 将用户信息封装的Token中,完成认证
OpenIdAuthenticationToken authenticationResult = new OpenIdAuthenticationToken(user, user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return OpenIdAuthenticationToken.class.isAssignableFrom(authentication);
}
public SocialUserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(SocialUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public UsersConnectionRepository getUsersConnectionRepository() {
return usersConnectionRepository;
}
public void setUsersConnectionRepository(UsersConnectionRepository usersConnectionRepository) {
this.usersConnectionRepository = usersConnectionRepository;
}
}
MyConnecitonSignUp
@Component
public class MyConnecitonSignUp implements ConnectionSignUp {
@Override
public String execute(Connection<?> connection) {
// 根据社交用于信息默认创建用户并返回用户的唯一标识
return connection.getDisplayName();
}
}
public class MySpringSocialConfigurer extends SpringSocialConfigurer {
private String filterProcessesUrl;
public MySpringSocialConfigurer(String filterProcessesUrl) {
this.filterProcessesUrl = filterProcessesUrl;
}
@Override
protected <T> T postProcess(T object) {
SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);
filter.setFilterProcessesUrl(filterProcessesUrl);
return (T) filter;
}
}
SocialConfig
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private MyConnecitonSignUp myConnecitonSignUp;
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
// Encryptors.noOpText() 对数据不加密
JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
// 只能设置表前缀,但是不能修改表名
jdbcUsersConnectionRepository.setTablePrefix("");
if (myConnecitonSignUp != null) {
jdbcUsersConnectionRepository.setConnectionSignUp(myConnecitonSignUp);
}
return jdbcUsersConnectionRepository;
}
@Bean
public MySpringSocialConfigurer mySpringSocialConfigurer() {
MySpringSocialConfigurer springSocialConfigurer = new MySpringSocialConfigurer("/qqLogin");
springSocialConfigurer.userIdSource(getUserIdSource());
springSocialConfigurer.signupUrl("/signup");
return springSocialConfigurer;
}
@Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
@Bean
public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
return new ProviderSignInUtils(connectionFactoryLocator, getUsersConnectionRepository(connectionFactoryLocator));
}
@Bean
public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) {
return new ConnectController(connectionFactoryLocator, connectionRepository);
}
}
OpenIdAuthenticationSecurityConfig
@Component
public class OpenIdAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private SocialUserDetailsService userDetailsService;
@Autowired
private UsersConnectionRepository usersConnectionRepository;
@Override
public void configure(HttpSecurity http) throws Exception {
OpenIdAuthenticationFilter openIdAuthenticationFilter = new OpenIdAuthenticationFilter("/authentication/openId");
openIdAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
openIdAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
openIdAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
OpenIdAuthenticationProvider provider = new OpenIdAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setUsersConnectionRepository(usersConnectionRepository);
http
.authenticationProvider(provider)
.addFilterAfter(openIdAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
ResourceServerConfiguration
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Autowired
private OpenIdAuthenticationSecurityConfig openIdAuthenticationSecurityConfig;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.apply(smsCodeAuthenticationSecurityConfig)
.and()
.apply(openIdAuthenticationSecurityConfig)
.and()
.formLogin()
.loginPage("/authentication/form")
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAuthenticationFailureHandler);
http.authorizeRequests()
.antMatchers("/login", "/authentication/form", "/authentication/mobile", "/authentication/openId", "/code/sms").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable();
}
}
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory() // 使用in-memory存储
.withClient("clientId")
.secret(new BCryptPasswordEncoder().encode("clientSecret"))
.authorizedGrantTypes("authorization_code")
.scopes("all")
.redirectUris("http://www.baidu.com");
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
}
}
@Slf4j
@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("login sucesssful {}", objectMapper.writeValueAsString(authentication));
String header = request.getHeader("Authorization");
if (header == null || !header.toLowerCase().startsWith("basic ")) {
throw new UnapprovedClientAuthenticationException("请求头中没有clientId");
}
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String clientId = tokens[0];
String clientSecret = tokens[1];
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (clientDetails == null) {
throw new UnapprovedClientAuthenticationException("clientId配置信息不存在,clientId=" + clientId);
} else if (!new BCryptPasswordEncoder().matches(clientSecret, clientDetails.getClientSecret())) {
throw new UnapprovedClientAuthenticationException("clientSecret不匹配,clientId=" + clientId);
}
// grantType 为自定义的"custom"
TokenRequest tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "custom");
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(accessToken));
}
/**
* Decodes the header into a username and password.
*
* @throws BadCredentialsException if the Basic header is not present or is not valid
* Base64
*/
private String[] extractAndDecodeHeader(String header, HttpServletRequest request)
throws IOException {
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.getDecoder().decode(base64Token);
}
catch (IllegalArgumentException e) {
throw new BadCredentialsException(
"Failed to decode basic authentication token");
}
String token = new String(decoded, "UTF-8");
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
}
return new String[] { token.substring(0, delim), token.substring(delim + 1) };
}
}
@Component
public class MyUserDetailsService implements UserDetailsService, SocialUserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
@Override
public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
return new SocialUser(userId, passwordEncoder.encode("123456"), true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN"));
}
public static void main(String[] args) {
System.out.println(new BCryptPasswordEncoder().encode("123456"));
}
}
providerId和openId 对应于数据库中UserConnection表的providerId和providerUserId,UserConnection表中的数据是社交登录时保存下来的。
三:标准模式获取token
标准模式获取token只需要设置社交认证过滤器SocialAuthenticationFilter认证成功后对应的成功处理器即可。
在SocialConfig中创建SpringSocialConfigurer实例时将处理器赋值,然后SpringSocialConfigurer#postProcess() 方法中调用处理器的process()方法,处理器就是为SocialAuthenticationFilter设AuthenticationSuccessHandler。
public interface SocialAuthenticationFilterPostProcessor {
void process(SocialAuthenticationFilter socialAuthenticationFilter);
}
App 社交登录成功后的处理逻辑,调用认证成功处理器,生成并返回token
@Component
public class AppSocialAuthenticationFilterPostProcessor implements SocialAuthenticationFilterPostProcessor {
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Override
public void process(SocialAuthenticationFilter socialAuthenticationFilter) {
// 设置认证过滤器认证成功后的处理逻辑
socialAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
}
}
public class MySpringSocialConfigurer extends SpringSocialConfigurer {
private String filterProcessesUrl;
private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;
public MySpringSocialConfigurer(String filterProcessesUrl) {
this.filterProcessesUrl = filterProcessesUrl;
}
@Override
protected <T> T postProcess(T object) {
SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);
filter.setFilterProcessesUrl(filterProcessesUrl);
// 设置app社交登录成功后的处理
if (socialAuthenticationFilterPostProcessor != null) {
socialAuthenticationFilterPostProcessor.process(filter);
}
return (T) filter;
}
public SocialAuthenticationFilterPostProcessor getSocialAuthenticationFilterPostProcessor() {
return socialAuthenticationFilterPostProcessor;
}
public void setSocialAuthenticationFilterPostProcessor(SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor) {
this.socialAuthenticationFilterPostProcessor = socialAuthenticationFilterPostProcessor;
}
}
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private MyConnecitonSignUp myConnecitonSignUp;
@Autowired
private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
// Encryptors.noOpText() 对数据不加密
JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
// 只能设置表前缀,但是不能修改表名
jdbcUsersConnectionRepository.setTablePrefix("");
if (myConnecitonSignUp != null) {
jdbcUsersConnectionRepository.setConnectionSignUp(myConnecitonSignUp);
}
return jdbcUsersConnectionRepository;
}
@Bean
public MySpringSocialConfigurer mySpringSocialConfigurer() {
MySpringSocialConfigurer springSocialConfigurer = new MySpringSocialConfigurer("/qqLogin");
springSocialConfigurer.userIdSource(getUserIdSource());
springSocialConfigurer.signupUrl("/signup");
// 设置app社交登录成功后的处理逻辑
springSocialConfigurer.setSocialAuthenticationFilterPostProcessor(socialAuthenticationFilterPostProcessor);
return springSocialConfigurer;
}
@Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
@Bean
public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
return new ProviderSignInUtils(connectionFactoryLocator, getUsersConnectionRepository(connectionFactoryLocator));
}
@Bean
public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) {
return new ConnectController(connectionFactoryLocator, connectionRepository);
}
}
对于标准模式获取token,App端只需要通过SDK获取到code值,然后调用服务器的社交登录地址"/<filterProcessesUrl>/<providerId>"
即可拿到服务器对应的token了。
四:App社交账号注册
用户首次使用社交账号登录时,UserConnection表中是没有记录的,当用户社交账号登录时需要往UserConnection表中插入一条数据。
-
当App通过第三方的SDK获取到授权码code时,可以通过调用自己服务器http://www.example.com:8080/qqLogin/weixin?code=xxxx 并携带Authorization和deviceId两个请求头去请求接口,此时服务器会跳转到http://www.example.com/social/signUp接口,当访问social/signUp接口时服务器会返回401响应码并且会返回第三方用户的一些基本信息
-
当App收到social/signUp接口响应时再去引导用户去注册或者绑定,当注册或者绑定完成后再调用服务器注册接口"/user/regist", 并携带Content-Type和deviceId请求头,并携带username请求参数去调用注册接口,注册接口会将第三方的用户信息插入到UserConnection表中
public class AppSecurityException extends RuntimeException {
private static final long serialVersionUID = 1L;
public AppSecurityException(String msg) {
super(msg);
}
}
@Component
public class AppSignupUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UsersConnectionRepository usersConnectionRepository;
@Autowired
private ConnectionFactoryLocator connectionFactoryLocator;
public void saveConnectionData(WebRequest request, ConnectionData connectionData) {
redisTemplate.opsForValue().set(getKey(request), connectionData, 10, TimeUnit.MINUTES);
}
public void doPostSignUp(WebRequest request, String userId) {
String key = getKey(request);
if (!redisTemplate.hasKey(key)) {
throw new AppSecurityException("无法找到缓存的第三方社交账号信息");
}
ConnectionData connectionData = (ConnectionData) redisTemplate.opsForValue().get(key);
Connection<?> connection = connectionFactoryLocator.getConnectionFactory(connectionData.getProviderId()).createConnection(connectionData);
usersConnectionRepository.createConnectionRepository(userId).addConnection(connection);
redisTemplate.delete(key);
}
private String getKey(WebRequest request) {
String deviceId = request.getHeader("deviceId");
if (StringUtils.isEmpty(deviceId)) {
throw new AppSecurityException("设备id不能为空");
}
return "connectiondata:" + deviceId;
}
}
/**
* 所有bean初始化前后都会经过这个类的方法
* @author Administrator
*
*/
@Component
public class SpringSocialBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("mySpringSocialConfigurer".equals(beanName)) {
MySpringSocialConfigurer configurer = (MySpringSocialConfigurer) bean;
//三方用户openid不存在于user_connection表时,重定向的路径
configurer.signupUrl("/social/signup");
return configurer;
}
return bean;
}
}
@Component
public class AppSocialAuthenticationFilterPostProcessor implements SocialAuthenticationFilterPostProcessor {
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Override
public void process(SocialAuthenticationFilter socialAuthenticationFilter) {
socialAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
}
}
@RestController
public class AppSecurityController {
@Autowired
private ProviderSignInUtils providerSignInUtils;
@Autowired
private AppSignupUtils appSignupUtils;
@GetMapping("/social/signUp")
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public SocialUserInfo getSocialUserInfo(HttpServletRequest request){
SocialUserInfo userInfo = new SocialUserInfo();
Connection<?> connection = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));
if (connection != null) {
userInfo.setProviderId(connection.getKey().getProviderId());
userInfo.setProviderUserId(connection.getKey().getProviderUserId());
userInfo.setNickname(connection.getDisplayName());
userInfo.setHeadimg(connection.getImageUrl());
appSignupUtils.saveConnectionData(new ServletWebRequest(request), connection.createData());
}
return userInfo;
}
}
@Data
@ToString
@RequiredArgsConstructor
public class SocialUserInfo {
private String providerId;
private String providerUserId;
private String nickname;
private String headimg;
}
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Autowired
private OpenIdAuthenticationSecurityConfig openIdAuthenticationSecurityConfig;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.apply(smsCodeAuthenticationSecurityConfig)
.and()
.apply(openIdAuthenticationSecurityConfig)
.and()
.formLogin()
.loginPage("/authentication/form")
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAuthenticationFailureHandler);
http.authorizeRequests()
.antMatchers("/login", "/authentication/form", "/authentication/mobile", "/authentication/openId", "/code/sms", "/social/signUp").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable();
}
}
public class User {
public interface UserSimpleView{};
public interface UserDetailView extends UserSimpleView{};
private String id;
private String username;
private String password;
private Date birthday;
@JsonView(UserSimpleView.class)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@JsonView(UserDetailView.class)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@JsonView(UserSimpleView.class)
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@JsonView(UserSimpleView.class)
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + "]";
}
}
@Slf4j
@RestController
public class UserController {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ProviderSignInUtils providerSignInUtils;
@Autowired
private AppSignupUtils appSignUpUtils;
@PostMapping("/user/regist")
public void regist(User user, HttpServletRequest request) {
// 不管是注册用户还是绑定用户,都会拿到一个用户唯一标识
String userId = user.getUsername();
// 浏览器注册用providerSignInUtils
// providerSignInUtils.doPostSignUp(userId, new ServletWebRequest(request));
// app注册用AppSignUpUtils
appSignUpUtils.doPostSignUp(new ServletWebRequest(request), userId);
}
@GetMapping(value = "/user/me", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String me() throws JsonProcessingException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
return objectMapper.writeValueAsString(principal);
}
}