什么是单点登陆系统(SSO)?

什么是单点登录系统

单点登录的英文名叫做:Single Sign On(简称SSO)。
在初学/以前的时候,一般我们就单系统,所有的功能都在同一个系统上。
什么是单点登陆系统(SSO)?
后来,我们为了合理利用资源和降低耦合性,于是把单系统拆分成多个子系统。
什么是单点登陆系统(SSO)?
比如阿里系的淘宝和天猫,很明显地我们可以知道这是两个系统,但是你在使用的时候,登录了天猫,淘宝也会自动登录。
什么是单点登陆系统(SSO)?
简单来说,单点登录就是在多个系统中,用户只需一次登录,各个系统即可感知该用户已经登录。

单点登录系统特点分析

HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一个用户。于是乎:服务器向用户浏览器发送了一个名为JESSIONID的Cookie,它的值是Session的id值。其实Session是依据Cookie来识别是否是同一个用户。
一般我们单系统实现登录会这样做:
1、 登录:将用户信息保存在Session对象中如果在Session对象中能查到,说明已经登录如果在Session对象中查不到,说明没登录(或者已经退出了登录)
2、注销(退出登录):从Session中删除用户的信息记住我(关闭掉浏览器后,重新打开浏览器还能保持登录状态):配合Cookie来用。

多系统登录的问题与解决

一 Session不共享问题
解决系统之间Session不共享问题有一下几种方案:
1、 Tomcat集群Session全局复制(集群内每个tomcat的session完全同步)【会影响集群的性能呢,不建议】
2、根据请求的IP进行Hash映射到对应的机器上(这就相当于请求的IP一直会访问同一个服务器)【如果服务器宕机了,会丢失了一大部分Session的数据,不建议】
3、把Session数据放在Redis中(使用Redis模拟Session)【建议】
SSO系统生成一个token,并将用户信息存到Redis中,并设置过期时间其他系统请求SSO系统进行登录,得到SSO返回的token,写到Cookie中每次请求时,Cookie都会带上,拦截器得到token,判断是否已经登录

到这里,其实我们会发现其实就两个变化:
什么是单点登陆系统(SSO)?

将登陆功能抽取为一个系统(SSO),其他系统请求SSO进行登录本来将用户信息存到Session,现在将用户信息存到Redis。

二.Cookie跨域的问题
比如说,我们请求https://www.google.com/时,浏览器会自动把google.com的Cookie带过去给google的服务器,而不会把https://www.baidu.com/的Cookie带过去给google的服务器。
这就意味着,由于域名不同,用户向系统A登录后,系统A返回给浏览器的Cookie,用户再请求系统B的时候不会将系统A的Cookie带过去。
针对Cookie存在跨域问题,有几种解决方案:

1、服务端将Cookie写到客户端后,客户端对Cookie进行解析,将Token解析出来,此后请求都把这个Token带上就行了多个域名共享Cookie,
2、在写到客户端的时候设置Cookie的domain。
3、将Token保存在SessionStroage中(不依赖Cookie就没有跨域的问题了)
三CAS原理(认证中心)
CAS (Central Authentication Service),下面说说CAS是怎么搞的。一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建会话,例如

什么是单点登陆系统(SSO)?

##具体实现

第一步:创建项目聚合工程
创建聚合工程的目的是对项目中的资源(例如一些依赖)进行统一管理,多个项目module之间共享资源.
这次项目的maven工程结构如下:
|—04-jt-sso
|–sso-auth #认证服务器
|–sso-resource #资源服务器
|–pom.xml #公共依赖及版本管理
什么是单点登陆系统(SSO)?
第二步:创建几个单独的系统。
1、两个工具类
第一步:定义JWT工具类,用于创建,解析,验证token,代码如下:
package sso.auth.util;
public class JwtUtils {
private static String secret=“AAABBBCCCDDDEEE”;
/*基于负载和算法创建token信息/
public static String generatorToken(Map<String,Object> map){
return Jwts.builder()
.setClaims(map)
.setExpiration(new Date(System.currentTimeMillis()+30601000))
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256,secret)
.compact();//签约,创建token
}
/*解析token获取数据/
public static Claims getClaimsFromToken(String token){
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
/*判定token是否失效/
public static boolean isTokenExpired(String token){
Date expiration=getClaimsFromToken(token).getExpiration();
return expiration.before(new Date());
}
}
第二步:定义Web工具类,用于向客户端响应json数据
package sso.auth.util;
public class WebUtils {
public static void writeJsonToClient(HttpServletResponse response, Map<String,Object> map)
throws IOException {
//1设置响应数据的编码
response.setCharacterEncoding(“utf-8”);
//2告诉浏览器响应数据的内容类型以及编码
response.setContentType(“application/json;charset=utf-8”);
//3获取输出流对象
PrintWriter out=response.getWriter();
//4 将map转换为json数据
String result=new ObjectMapper().writeValueAsString(map);
//5 将数据响应到客户端
out.println(result);
out.flush();
}
}
安全配置类:
package sso.auth.config;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//1.关闭跨域攻击
http.csrf().disable();
//2.配置登录url(登录表单使用哪个页面)
http.formLogin()
.successHandler(authenticationSuccessHandler())
.failureHandler(authenticationFailureHandler());
//设置需要认证与拒绝访问的异常处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
//3.放行登录url(不需要认证就可以访问)
http.authorizeRequests()
.anyRequest().authenticated();//除了以上资源必须认证才可访问
}
//认证成功处理器
public AuthenticationSuccessHandler authenticationSuccessHandler(){
return (httpServletRequest, httpServletResponse,authentication)-> {
User principal = (User)authentication.getPrincipal();
Map<String,Object> map=new HashMap<>();
map.put(“state”,200);
map.put(“message”,“Login ok”);
Map<String,Object> jwtMap=new HashMap<>();
jwtMap.put(“username”, principal.getUsername());
List authorities = new ArrayList<>();
principal.getAuthorities().forEach((authority)-> {
authorities.add(authority.getAuthority());
});
jwtMap.put(“authorities”,authorities);
String token=JwtUtils.generatorToken(jwtMap);
map.put(“token”, token);
WebUtils.writeJsonToClient(httpServletResponse,map);
};
}
//认证失败处理器
public AuthenticationFailureHandler authenticationFailureHandler(){
return (httpServletRequest, httpServletResponse, e) -> {
Map<String,Object> map=new HashMap<>();
map.put(“state”,500);
map.put(“msg”,“username or password error”);
WebUtils.writeJsonToClient(httpServletResponse,map);
};
}
//没有认证时执行DefaultAuthenticationEntryPoint对象
public AuthenticationEntryPoint authenticationEntryPoint(){
return (httpServletRequest, httpServletResponse, e)->{
Map<String,Object> map=new HashMap<>();
map.put(“state”,401);//SC_UNAUTHORIZED 的值为401
map.put(“message”,“请先登录再访问”);
WebUtils.writeJsonToClient(httpServletResponse,map);
};
}
}
认证逻辑对象

上一篇:sso单点登陆


下一篇:单点登录