java单点登录SSO教程(含源码和视频教程)

单点登录(SSO):SSO是指在多个应用系统中个,用户只需要登陆一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一用户的登陆的机制。简单的说登录一次之后的子系统或关联项目则无需再次登录。实现原理

单点登录工作原理

在这里插入图片描述

1.用户从浏览器输入需要请求的资源,如http://www.user.com/user/wel
2.请求到达后端服务之后http://www.user.com/user/wel发现没有携带jsessionid信息
3.资源服务器将请求转发到sso服务器并写单回调地址
4.认证服务接到请求之后响应浏览器一个登陆表单
5.用户在浏览器中输入账号密码提交到认证服务器
6.认证服务器校验账号密码如果失败则返回到第4步,服务端生产全局jsessionid并请求回调地址
7.回调所在资源接收到请求之后,发现携带有jsessionid则复制一份保存在当前服务器中将资源返回给浏览器
8.如果用户请求其他系统资源如订单模块
9.此时在认证项目中发现有jsessionid则直接回调订单模块资源地址,并将jsessionid保存到服务器中
10.用户无需登录就可直接访问资源

项目目录结构

├─src
│  ├─main
│  │  ├─java
│  │  └─resources
├─sso-client-order
│  ├─src
│  │  ├─main
│  │  │  ├─java
│  │  │  │  └─com
│  │  │  │      └─mrduan
│  │  │  │          └─order
│  │  │  │              ├─config
│  │  │  │              ├─controller
│  │  │  │              ├─interceptor
│  │  │  │              └─util
│  │  │  └─resources
│  │  │      ├─static
│  │  │      └─templates
├─sso-client-user
│  ├─src
│  │  ├─main
│  │  │  ├─java
│  │  │  │  └─com
│  │  │  │      └─mrduan
│  │  │  │          └─user
│  │  │  │              ├─config
│  │  │  │              ├─controller
│  │  │  │              ├─interceptor
│  │  │  │              └─util
│  │  │  └─resources
│  │  │      ├─static
│  │  │      └─templates
└─sso-server
    ├─src
    │  ├─main
    │  │  ├─java
    │  │  │  └─com
    │  │  │      └─mrduan
    │  │  │          └─ssoserver
    │  │  │              ├─controller
    │  │  │              └─util
    │  │  └─resources
    │  │      ├─static
    │  │      │  └─images
    │  │      └─templates

项目依赖

sso pom.xml



    4.0.0

    com.mrduan
    sso
    pom
    1.0-SNAPSHOT
    
        sso-server
        sso-client-order
        sso-client-user
    

    sso-server
    Demo project for Spring Boot
    
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.0.RELEASE
        
    

    
        UTF-8
        UTF-8
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

        
            com.auth0
            java-jwt
            3.2.0
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

服务端代码

登录控制器SSOController

package com.mrduan.ssoserver.controller;

import com.mrduan.ssoserver.util.JwtUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

@Controller
public class SSOController {

    List list = new ArrayList<>();

    /**
     * 预登录
     * @param url
     * @param request
     * @param model
     * @return
     */
    @RequestMapping(value = "/preLogin",method = RequestMethod.GET)
    public String preLogin(String url, HttpServletRequest request, Model model){
        System.out.println("请求的地址:" + url);

        HttpSession session = request.getSession(false);

        // 如果没有登录则跳转到登录页面
        if(session == null){
            model.addAttribute("url",url);
            return "login";
        }
        else{
            String token = (String)session.getAttribute("token");
            return "redirect:http://"+url+"?token="+token;
        }
    }

    @RequestMapping("/login")
    public String login(String username, String password, String url,  HttpServletRequest request){
        // TODO 登录这里写死的,需要与数据库等整合的在修改这里即可
        if("Bruce".equals(username) && "123456".equals(password)){

            // 创建token
            String token = null;
            try {
                token = JwtUtil.createJwt();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            System.out.println("token信息"+token);

            HttpSession session = request.getSession();
            request.getSession().setAttribute("token",token);
            list.add(token);
            return "redirect:http://"+url;
        }else{
            return "login";
        }
    }

    @RequestMapping("/checkToken")
    @ResponseBody
    public String checkToken(String token) {
        System.out.println(JwtUtil.verifyJwt(token));
        if (list.contains(token)&&JwtUtil.verifyJwt(token)) {
            return "correct";
        }
        return "incorrect";
    }
}

token创建工具

package com.mrduan.ssoserver.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.io.UnsupportedEncodingException;
import java.util.Date;

public class JwtUtil {

    /**
     * 创建JWT
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String createJwt() throws UnsupportedEncodingException {
        Algorithm al = Algorithm.HMAC256("secretkey");
        
        // 这里也是写死的,需要与数据库整合的修改这里即可
        String token = JWT.create()
                .withIssuer("Burce")
                .withSubject("SSO")
                .withClaim("userid","1234")
                .withExpiresAt(new Date(System.currentTimeMillis()+360000))
                .sign(al);
        return token;
    }

    /**
     * 验证jwt
     * @param token
     * @return
     */
    public static boolean verifyJwt(String token){

        try {
            Algorithm algorithm = null;
            algorithm = Algorithm.HMAC256("secretkey");
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (JWTVerificationException e){
            e.printStackTrace();
            System.out.println("校验失败");
        }
        return false;
    }
}

项目启动类

package com.mrduan.ssoserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SsoServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SsoServerApplication.class,args);
    }
}

配置文件

server:
    port: 8090

到此服务端的代码编写完毕
注意:前端代码没有在这里贴出来,如有需要,文章下方有资源链接

用户模块代码

项目sso-clinet-user
视图控制器类UserController

package com.mrduan.user.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/wel")
    public String wel(){
        return "wel";
    }
}

session拦截器

package com.mrduan.user.interceptor;

import com.mrduan.user.util.HttpUtil;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class SessionInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);

        // 判断session是否有jsessionid
        if(session!=null && session.getAttribute("login").equals("login")){
            return true;
        }

        String token = request.getParameter("token");
        System.out.println("得到token信息"+token);

        //当token信息不为空的是
        if(token != null){
            String reqUrl = "http://www.sso.com:8090/checkToken";
            String content = "token="+token;
            String result = HttpUtil.sendReq(reqUrl,content);
            if ("correct".equals(result)) {
                // token有效
                // 子系统的局部session对象
                request.getSession().setAttribute("login", "login");
                return true;
            }
        }

        // www.user.com:8081/user/wel   发现既没有jsessionid也没有token,只能做页面的跳转,到认证中心去认证
        response.sendRedirect("http://www.sso.com:8090/preLogin?url=www.user.com:8081/user/wel");
        return false;
    }
}

资源请求工具类

package com.mrduan.user.util;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpUtil {
    public static String sendReq(String reqUrl,String content) throws Exception {
        URL url=new URL(reqUrl);
        HttpURLConnection conn= (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);
        byte[] b=content.getBytes("UTF-8");
        conn.getOutputStream().write(b);
        conn.connect();
        InputStream in =conn.getInputStream();
        return Stream2String(in);
    }

    public static String Stream2String(InputStream in){
        if(in!=null) {
            try {
                BufferedReader tBufferedReader = new BufferedReader(new InputStreamReader(in));
                StringBuffer tStringBuffer = new StringBuffer();
                String sTempOneLine = new String("");
                while ((sTempOneLine = tBufferedReader.readLine()) != null) {
                    tStringBuffer.append(sTempOneLine);
                }
                return tStringBuffer.toString();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return null;
    }
}

拦截器配置类

package com.mrduan.user.config;

import com.mrduan.user.interceptor.SessionInterceptor;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootConfiguration
public class UserWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截地址为/user/*
        registry.addInterceptor(sessionInterceptor()).addPathPatterns("/user/*");
    }

    @Bean
    public SessionInterceptor sessionInterceptor(){
        return new SessionInterceptor();
    }

}

项目启动类

package com.mrduan.user;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class UserSsoClientApplication{

    public static void main(String[] args) {
        SpringApplication.run(UserSsoClientApplication.class,args);
    }
}

项目配置文件

server:
  port: 8081

注意:前端代码没有在这里贴出来,如有需要,文章下方有资源链接

订单模块代码

订单模块的代码与用户模块代码几乎一致
项目sso-clinet-user
视图控制器类OrderController

package com.mrduan.order.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("order")
//www.order.com:8082/order/wel
public class OrderController {
    @RequestMapping("wel")
    public String wel(){
        return "wel";
    }
}

session拦截器

package com.mrduan.order.interceptor;

import com.mrduan.order.util.HttpUtil;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class SessionInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("I'm here");
        HttpSession session =request.getSession(false);
        if (session!=null&&session.getAttribute("login").equals("login")) {
            return true;
        }

        String token = request.getParameter("token");
        if (token != null) {
            String reqUrl = "http://www.sso.com:8090/checkToken";
            String content = "token=" + token;
            String result = HttpUtil.sendReq(reqUrl, content);
            if ("correct".equals(result)) {
                request.getSession().setAttribute("login", "login");
                return true;
            }
        }


        response.sendRedirect("http://www.sso.com:8090/preLogin?url=www.order.com:8082/order/wel");
        return false;
    }
}

资源请求工具类

package com.mrduan.order.util;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpUtil {
    public static String sendReq(String reqUrl,String content) throws Exception {
        URL url=new URL(reqUrl);
        HttpURLConnection conn= (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);
        byte[] b=content.getBytes("UTF-8");
        conn.getOutputStream().write(b);
        conn.connect();
        InputStream in =conn.getInputStream();
        return Stream2String(in);
    }

    public static String Stream2String(InputStream in){
        if(in!=null) {
            try {
                BufferedReader tBufferedReader = new BufferedReader(new InputStreamReader(in));
                StringBuffer tStringBuffer = new StringBuffer();
                String sTempOneLine = new String("");
                while ((sTempOneLine = tBufferedReader.readLine()) != null) {
                    tStringBuffer.append(sTempOneLine);
                }
                return tStringBuffer.toString();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return null;
    }
}

拦截器配置类

package com.mrduan.order.config;


import com.mrduan.order.interceptor.SessionInterceptor;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootConfiguration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getSessionInterceptor()).addPathPatterns("/order/*");
    }

    @Bean
    public SessionInterceptor getSessionInterceptor(){
        return new SessionInterceptor();
    }
}

项目启动类

package com.mrduan.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SsoOrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(SsoOrderApplication.class,args);
    }
}

项目配置文件

server:
  port: 8082

注意:前端代码没有在这里贴出来,如有需要,文章下方有资源链接

到此我我们代码部分编写完毕

配置DNS解析

我们这里使用的是域名访问的因此需要配置host文件

mac环境配置

sudo vi /etc/hosts

在最后面追加3行

127.0.0.1   www.sso.com
127.0.0.1   www.user.com
127.0.0.1   www.order.com

windows环境配置

文件位置
C:\Windows\System32\drivers\etc

# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

# localhost name resolution is handled within DNS itself.
#   127.0.0.1       localhost
#   ::1             localhost

127.0.0.1   www.sso.com
127.0.0.1   www.user.com
127.0.0.1   www.order.com

新增三行记录

127.0.0.1   www.sso.com
127.0.0.1   www.user.com
127.0.0.1   www.order.com

测试验证

到此配置也完成,我们分别启动这三台服务器测试一下效果
在浏览器输入http://www.user.com:8081/user/wel

在这里插入图片描述

输入之后由于我们没有登录则直接跳转到登录页面了
http://www.sso.com:8090/preLogin?url=www.user.com:8081/user/wel
并且携带了我们先去输入的地址
我们输入用户名和密码
Bruce/123456
在这里插入图片描述

登录之后可以发现,地址重新回到了www.user.com:8081/user/wel,并且携带了token信息
接下来我们尝试访问订单模块
在浏览器输入http://www.order.com:8082/order/wel
在这里插入图片描述

这一次无需再次登录直接定位到了资源页面
对比token发现这2个页面的token是一样的
在浏览器中查看cookie信息可以看到jsessionid信息
在这里插入图片描述

同时在www.sso.com域下也产生了一个cookie
在这里插入图片描述

到此我们的验证也完成

项目源代码
视频教程

你可能感兴趣的:(java单点登录SSO教程(含源码和视频教程))