sso单点登录实现
Posted 码农小黑
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了sso单点登录实现相关的知识,希望对你有一定的参考价值。
一、sso单点登录原理
单点登录全称Single Sign On(简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分
结合本项目案例,画了下SSO单点登录原理图,如下:
二、sso单点登录实现
1.sso-server单点登录服务端
1.sso-server服务端包含三个模块:
sso-common通用模块
sso-entity实体类模块
sso-web核心web模块
其中sso-common和sso-entity作为sso-client的依赖
2.核心代码
/**
* Created by wly on 2018/11/19.
*/
@Controller
@RequestMapping("/sso")
public class SsoController extends BaseController{
@Autowired
private UserService userService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private SpringSsoAutoConfig ssoAutoConfig;
@RequestMapping("/login")
public String login(@RequestParam(value = "fromUrl",required = false) String fromUrl, Model model) throws IOException {
String token = getCookie( getRequest(), ParamEnum.PARAM_COKIE_NAME.getValue() );
if(StringUtils.isNotBlank( token )){
fromUrl = contact( fromUrl, ParamEnum.PARAM_TOKEN.getValue(),token );
return "redirect:"+fromUrl;
}
if(StringUtils.isNotBlank( ssoAutoConfig.getSsoServerLoginUrl() )){
String url = contact( ssoAutoConfig.getSsoServerLoginUrl(), ParamEnum.PARAM_FROM_URL.getValue(), fromUrl );
return "redirect:"+url;
}else {
model.addAttribute( "fromUrl",fromUrl );
return "login";
}
}
@RequestMapping("/doLogin")
public String doLogin(ModelMap model , @RequestParam(value = "email",required = false) String email,
@RequestParam(value = "password",required = false) String password,
@RequestParam(value = "phone",required = false) String phone,
@RequestParam(value = "fromUrl",required = false) String fromUrl) throws IOException {
User user = null;
Boolean bl = false;
Map<String,Object> map = new HashMap<>( );
if(StringUtils.isNotBlank( email )){
user = userService.findByEmail( email );
bl = ValidatePassword( user, password );
}else if(StringUtils.isNotBlank( phone )){
user = userService.findByPhone( phone );
bl = ValidatePassword( user, password );
}
if(bl){
//账号密码正确
String token = UUID.randomUUID().toString();
//getResponse().setHeader("Access-Control-Allow-Origin", "*"); 允许跨域
writeCookie( getResponse(),ParamEnum.PARAM_COKIE_NAME.getValue(),token,ssoAutoConfig.getSsoCookieTimeout() );
redisTemplate.opsForValue().set( token,user,ssoAutoConfig.getSsoCookieTimeout(),TimeUnit.SECONDS );
String redirectUrl = contact( fromUrl,ParamEnum.PARAM_TOKEN.getValue(),token );
return "redirect:"+redirectUrl;
}else{
//账号或者密码错误
//getResponse().setHeader("Access-Control-Allow-Origin", "*"); 允许跨域
model.addAttribute( "result","error");
model.addAttribute( "fromUrl",fromUrl );
return "login";
}
}
@ResponseBody
@RequestMapping("/validate")
public Object validateToken( @RequestParam(value = "sso_token",required = false) String token) throws IOException {
if(StringUtils.isNotBlank( token )){
Object obj = redisTemplate.opsForValue().get( token );
if(obj!=null){
//已登录 返回user对象
return obj;
}else{
//未登录或已失效
return null;
}
}else {
//未登录
return null;
}
}
}
如果系统未登录跳转到sso-server登录页面并携带来源url,在sso-server登录页面进行登录操作:
如果登录成功则生成唯一token并保存到cookie中,主意此时设置cookie的域名domain为sso.com ,设置过期时间,然后以token为key,用户信息为value存储到redis中,并设置过期时间,与cookie的过期时间一致
如果登录失败,则跳转到sso-server登录页面并提示错误
2.sso-client单点登录客户端
1.引入sso-server服务端的两个依赖
<dependency>
<groupId>com.sso.www</groupId>
<artifactId>sso-common</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.sso.www</groupId>
<artifactId>sso-entity</artifactId>
<version>1.0.0</version>
</dependency>
2.添加插件,使得sso-client在打包的时候能将依赖一起打包
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.4.1</version> <configuration>
<!-- get all project dependencies -->
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs> <!-- MainClass in mainfest make a executable jar -->
<archive> <manifest> <mainClass>util.Microseer</mainClass> </manifest> </archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- bind to the packaging phase -->
<phase>package</phase> <goals> <goal>single</goal> </goals>
</execution>
</executions>
</plugin>
使用maven 打包命令打包
install -Dmaven.test.skip=true
这样在target目录就会出现两个jar,以jar-with-dependencies.jar结尾的就是包含依赖的jar,另一个则不包含依赖,因为sso-client客户端还要被其他系统依赖,所以我们需要含有依赖的jar
3.核心代码:
/**
* Created by wly on 2018/11/20.
*/
public class LoginFilter implements Filter {
@Autowired
private SpringSsoAutoConfig springSsoAutoConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
User user = (User) request.getSession().getAttribute( SessionEnum.SESSION_LOGIN.getValue() );
String uri = request.getRequestURI();
System.out.println("uri=="+uri);
String ssoLoginUrl = springSsoAutoConfig.getSsoServerLoginUrl();
String url = ssoLoginUrl + "?"+ ParamEnum.PARAM_FROM_URL.getValue()+"="+springSsoAutoConfig.getSsoClientFromUrl()+uri;
String token = request.getParameter( ParamEnum.PARAM_TOKEN.getValue() );
if(user!=null){
filterChain.doFilter( servletRequest,servletResponse );
}else if(StringUtils.isNotBlank( token )){
postUrl(request,response,token,filterChain,url);
}else{
response.sendRedirect( url );
return;
}
}
@Override
public void destroy() {
}
public void postUrl(HttpServletRequest request, HttpServletResponse response, String token, FilterChain filterChain, String redirectUrl) throws IOException, ServletException {
//获取令牌 存储令牌和用户
Map<String,String> map = new HashMap<>( );
map.put( SessionEnum.SESSION_SSO_TOKEN.getValue(),token );
String jsonStr = UrlUtil.sendPost( springSsoAutoConfig.getSsoServerValidateUrl(), map );
if(StringUtils.isNotBlank( jsonStr )){
User user = JSONObject.parseObject( jsonStr, User.class );
request.getSession().setAttribute( SessionEnum.SESSION_LOGIN.getValue(),user );
filterChain.doFilter( request,response );
}else {
response.sendRedirect( redirectUrl );
}
}
}
sso-client客户端添加自定义登录过滤器LoginFilter,拦截所有请求,如果已登录则直接放行,如果未登录则判断是否包含token,如果不包含token则重定向到sso-server服务端进行登录,如果包含token则发送post请求给sso-server服务端验证token的有效性和正确性,并将用户信息返回,如果返回值为null则重定向到sso-server服务端进行登录,如果返回用户信息则将用户信息保存到session中,维护用户的登录状态
其他项目只要依赖sso-client就可以做登录拦截了,而不用每个项目都去写一个登录过滤器。
3.sso-yvpai测试系统
1.引入sso-client依赖
<dependency>
<groupId>com.client.www</groupId>
<artifactId>sso-client</artifactId>
<version>1.0.0</version>
</dependency>
2.登录过滤器sso-client已经写好了,所以这里的核心代码主要是登录成功后的跳转:
/**
* Created by wly on 2018/11/19.
*/
@Controller
@RequestMapping("/yvpai")
public class LoginController extends BaseController {
@Autowired
private SpringSsoAutoConfig springSsoAutoConfig;
@RequestMapping("/home")
public String home(Model model) {
model.addAttribute( "msg",springSsoAutoConfig.getSsoClientFromUrl()+"的首页" );
return "home";
}
}
登录成功后访问/yvpai/home 跳转到home.html页面
三、启动测试
我们使用whistle进行域名代理
如果对whistle不了解的同学可以参考我的另一篇博客web调试代理工具Whistle https://blog.csdn.net/abcwanglinyong/article/details/80264271
启动sso-server服务端
启动sso-client客户端
启动yvpai测试系统
启动完成以后访问www.client.com/home
此时因为没有登录会跳转到sso-server登录页面如下:
然后在访问我们的测试系统:www.yvpai.com/yvpai/home
发现此时不需要登录就可以直接访问了,如图:
注意:
1.为了保证数据安全,sso-server服务端应该对用户信息进行加密操作,sso-client客户端获取用户信息后再对其进行解密,而且用户信息应该只保存{id:1}部分,客户端获取id后再去数据库查询用户信息
2.sso-server应该提供操作用户的接口
单点注销
用户点击退出登录按钮:
1.sso-client客户端自己清除本地session
2.sso-client客户端发送post请求给sso-server服务端,服务端清除本地cookie以及redis中对应的信息,然后通知其他子系统清除本地session
关于单点注销这里就不实现了,有兴趣的同学可以自己实现以下。
以上是关于sso单点登录实现的主要内容,如果未能解决你的问题,请参考以下文章