OAuth协议
OAuth协议解决什么问题
- 需求场景
有一个服务,用于为微信拍摄的图片进行美化,这会涉及到一个问题,就是服务怎么去读取微信的数据,这要得到微信的授权。传统上,是把微信的用户名密码给服务,但这会导致以下一个问题- 服务可以访问微信的所有数据,无法指定访问范围
- 只有修改密码才能收回权限
- 增加了密码泄漏的风险
OAuth解决了上述问题,不直接给用户名密码,而是给token,token 中指定了服务范围和有效时间。
image.png
OAuth协议的角色
- Privoder:服务提供商
提供token- Authorization Server:认证服务器
认证用户的身份,并产生token - 资源服务器
保存用户资源
验证token
- Authorization Server:认证服务器
- Resource Owner:资源所有者
-
Client:第三方应用
image.png
OAuth协议的运行流程
image.pngOAuth授权模式
-
授权码模式
image.png
四种授权模式中,功能最完整,最严密的授权模式
是目前主流
- 特点
用户同意授权的动作是在认证服务器完成的
认证服务器不直接返回token,而是先返回授权码,由授权码去申请令牌
- 特点
-
密码模式
-
其他
- 简化模式
- 客户端模式
小结
OAuth解决了在不给认证信息的情况下,将app权限给第三方应用的场景。
Spring Social基本原理
在使用OAuth的情况下,获取到了微信、QQ等的授权,拿到了用户的基本信息,用这个基本信息作为APP的注册信息进行注册,达到使用QQ、微信登陆APP的效果。
image.pngSpring Social封装了上述1~7个步骤的流程。
image.pngSocialAuthenticationFilter
,实现了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);
}
image.png注意不要忘记把这个请求地址放到不需要权限控制的url列表中
这就是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操作都能使用。
退出登陆
-
如何退出登陆
-
Spring Security默认退出逻辑
- 使当前session失效
- 清除与当前用户相关的remember-me记录
- 清空当前SecurityContent
- 重定向到登陆页
-
相关配置
.logout()
.logoutUrl("/logout")//执行退出逻辑的url
.logoutSuccessUrl("/imooc-logout.html")//退出成功后跳转url
.and()
也可以使用.logoutSuccessHandler()
进行配置,其可以在退出成功后做更复杂的操作,如记录退出日志等。
注意,
.logoutSuccessHandler()
与logoutSuccessUrl()
只能二选一进行配置。
添加好友,备注:来自简书,一起 好好学习,天天向上
微信号
网友评论
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.cors().and().csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
.and().logout()
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1)
.maxSessionsPreventsLogin(true);
// @formatter:on
//以下这句就可以控制单个用户只能创建一个session,也就只能在服务器登录一次
// http.sessionManagement().maximumSessions(1).and().;
}
用的oauth2,我配置的不允许账号同时在线没有生效,不知道什么问题,我希望每次用户登陆都能获取新的的token,不知道怎么设置,楼主遇到过没有