技术架构

05 Spring Social开发第三方登陆(未完)

2018-04-22  本文已影响123人  Koko_滱滱

OAuth协议

OAuth协议解决什么问题

OAuth解决了上述问题,不直接给用户名密码,而是给token,token 中指定了服务范围和有效时间。


image.png

OAuth协议的角色

OAuth协议的运行流程

image.png

OAuth授权模式

小结

OAuth解决了在不给认证信息的情况下,将app权限给第三方应用的场景。

Spring Social基本原理

在使用OAuth的情况下,获取到了微信、QQ等的授权,拿到了用户的基本信息,用这个基本信息作为APP的注册信息进行注册,达到使用QQ、微信登陆APP的效果。

image.png

Spring Social封装了上述1~7个步骤的流程。

image.png

SocialAuthenticationFilter,实现了1~7步

使用Spring Social开发涉及到的若干组件


image.png

QQ登陆

使用第三方账户登陆系统,关键就是走一遍OAuth协议的流程,从服务提供商中获取到用户的基本信息,然后注册到自己的系统中。
//TODO

注册

//TODO

微信登陆

//TODO

绑定与解绑

//TODO

Session管理

用户名密码登陆、手机号短信登陆、QQ登陆、微信登陆,登陆成功后,用户信息是存储在服务器的session中的。
下面就来讲解一下session的基本管理。

session超时处理

用户在一段时间内没有操作,需要将用户从session中剔除掉,而此时用户再发起请求,又该如何处理。
这就是session超时相关的需要解决的问题。

在springboot中设置session超时时间非常简单,加个配置就行

server:
  session:
#  session超时时间,单位秒,默认30分钟
    timeout: 30

注意:如果我们设置的时间少于1分钟,那么超时时间就是一分钟。

下面我们来解决另一个问题,在session超时后,如果用户继续访问,我们得提示用户session超时,而不是其他的引导页跳转等不合适的信息。否则用户会想,我刚才明明已经认证过了,怎么又让我认证一遍。

我们添加以下配置

                .sessionManagement()
                .invalidSessionUrl("/session/invalid")
                .and()

当session超时后,跳转到指定页面
完整的配置类

package com.imooc.security.browser;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import com.imooc.security.core.authentication.AbstractChannelSecurityConfig;
import com.imooc.security.core.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import com.imooc.security.core.properties.SecurityConstants;
import com.imooc.security.core.properties.SecurityProperties;
import com.imooc.security.core.validate.code.ValidateCodeSecurityConfig;

/**
 * @author zhailiang
 *
 */
@Configuration
public class BrowserSecurityConfig extends AbstractChannelSecurityConfig {

    @Autowired
    private SecurityProperties securityProperties;

    @Qualifier("dataSource")
    @Autowired
    private DataSource dataSource;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    @Autowired
    private ValidateCodeSecurityConfig validateCodeSecurityConfig;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        applyPasswordAuthenticationConfig(http);

        http.apply(validateCodeSecurityConfig)
                .and()

                .apply(smsCodeAuthenticationSecurityConfig)
                .and()

                .rememberMe()
                .tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
                .userDetailsService(userDetailsService)
                .and()

                .sessionManagement()
                .invalidSessionUrl("/session/invalid")
                .and()

                .authorizeRequests()
                .antMatchers(
                        SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
                        SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
                        securityProperties.getBrowser().getLoginPage(),
                        SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()

                .csrf().disable();

    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
//      tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }

}

控制器中处理失效请求

    @GetMapping("/session/invalid")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public SimpleResponse sessionInvalid() {
        String message = "session失效";
        return new SimpleResponse(message);
    }

注意不要忘记把这个请求地址放到不需要权限控制的url列表中

image.png
这就是session超时失效后,客户端收到的消息

session并发控制

用户在A机器上登陆后,在未退出的情况下,又使用相同账号在B机器上进行了登陆。
我们需要用B登陆覆盖掉A登陆。

                .sessionManagement()
                .invalidSessionUrl("/session/invalid")
                .maximumSessions(1)
                .expiredSessionStrategy(new ImoocExpiredSessionStrategy(SecurityConstants.DEFAULT_SESSION_INVALID_URL))
                .and()
                .and()

我们配置.maximumSessions(1)表示同一个用户只允许有一个session,
然后配置expiredSessionStrategy处理当前session被剔除后的处理。

如果我们不是希望新的登陆去覆盖旧的登陆,而是不允许在旧的登陆未退出的情况下,让新的登陆成功,那么就要加一个配置

.maxSessionsPreventsLogin(true)//session数达到最大值后不允许新的session登陆

集群session管理

默认在单机的情况下,我们的session信息是存储在tomcat等中间件中的,此时如果不做任何处理,那么在集群环境下是会有问题的。
比如用户的登陆认证信息通过负载均衡给了A机器,此时明明已经认证成功了,但是下一个请求发送到了B机器上,B机器上的tomcat没有该用户的session信息,这就导致又得认证一遍,而我们还无法保证下次认证的时候真的是认证在B机器上。

上述问题是在做集群的时候需要考虑的。

解决思路:不把session信息存储在服务器上,而是放在一个独立的存储中。


image.png

spring-session项目就是用来完成上述的行为的,使用起来非常简单。
spring支持的外部存储包括

public enum StoreType {

    /**
     * Redis backed sessions.
     */
    REDIS,

    /**
     * Mongo backed sessions.
     */
    MONGO,

    /**
     * JDBC backed sessions.
     */
    JDBC,

    /**
     * Hazelcast backed sessions.
     */
    HAZELCAST,

    /**
     * Simple in-memory map of sessions.
     */
    HASH_MAP,

    /**
     * No session data-store.
     */
    NONE;

}

下面我们以使用Redis为例

spring:
  session:
#  session集群管理,none表示关闭
    store-type: REDIS

然后再配置下REDIS的连接地址,如果数localhost的6379端口,可以不配置。

注意,现在我们的session已经由redis进行管理了,被redis管理的java类需要实现序列化接口。

使用Redis代替服务器管理session后,对系统没有任何影响,原先的session操作都能使用。

退出登陆

                .logout()
                .logoutUrl("/logout")//执行退出逻辑的url
                .logoutSuccessUrl("/imooc-logout.html")//退出成功后跳转url
                .and()

也可以使用.logoutSuccessHandler()进行配置,其可以在退出成功后做更复杂的操作,如记录退出日志等。

注意,.logoutSuccessHandler()logoutSuccessUrl()只能二选一进行配置。

添加好友,备注:来自简书,一起 好好学习,天天向上


微信号
上一篇 下一篇

猜你喜欢

热点阅读