自己写SSO-基于Cookie的简单实现(二)

代码结构图

先看下面结构图

工程说明:

用SpringBoot搭建

  • sso-server:sso认证中心
  • sso-client: sso客户端
  • biz-app-A:业务系统A
  • biz-app-B:业务系统B
代码地址

SimpleSSO

hosts

mac系统下编辑/etc/hosts添加如下

1
2
3
127.0.0.1 a.biz.com
127.0.0.1 b.biz.com
127.0.0.1 sso.com

a.biz.com是系统A的域名,b.biz.com是系统B的域名,sso.com是认证系统域名。

源码

  • sso-server

    • 启动入口

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      /**
      * @author Liu Hailin
      * @create 2017-10-20 下午3:53
      **/
      @SpringBootApplication
      public class BootSsoServer {
      public static void main(String[] args) {
      SpringApplication.run( BootSsoServer.class,args );
      }
      }

    • 配置文件application.yml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      server:
      port: 9000
      session:
      timeout: 1 #设置session 1分钟

      spring:
      application:
      name: sso-server
      mvc:
      static-path-pattern: /static/**
      favicon:
      enabled: false
      servlet:
      load-on-startup: 1

      端口是9000,页面模板用freemarker,具体配置不详细介绍了。

    • 登录

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      /**
      * @author Liu Hailin
      * @create 2017-10-20 下午3:30
      **/
      @Controller
      @Slf4j
      @RequestMapping("/sso")
      public class LoginController {

      @Autowired
      private HttpServletRequest request;

      @Autowired
      private HttpServletResponse response;

      @GetMapping("/auth")
      public String auth(@RequestParam(value = "originUrl",required = false) String originUrl) throws IOException {

      String authToken = CookieUtil.getCookieByName( request,"simple_sso" );
      if(StringUtils.isEmpty( authToken )){
      if(!StringUtils.isEmpty( originUrl )){
      return "redirect:/sso/login?originUrl="+originUrl;
      }
      return "redirect:/sso/login";
      }

      response.sendRedirect( originUrl+"?token="+authToken );

      return null;
      }



      @GetMapping("/login")
      public String login(ModelMap map,@RequestParam(value = "originUrl",required = false) String originUrl) {
      map.put( "originUrl",originUrl );
      return "login";
      }


      @PostMapping("/login")
      public void login(@ModelAttribute User user, ModelMap map,@RequestParam(value = "originUrl") String originUrl)
      throws IOException {

      log.info( "User [{}] is login success.", user.getUserName() );

      String token = UUID.randomUUID().toString().replace("-", "");

      Cookie tokenCookie = new Cookie("simple_sso", token);

      tokenCookie.setDomain( "sso.com" );
      tokenCookie.setHttpOnly( true );
      tokenCookie.setPath( "/" );
      tokenCookie.setMaxAge(10*60);

      response.addCookie( tokenCookie );

      response.sendRedirect( originUrl+"?token="+token );
      }
      }
    • 登录页面login.ftl

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <form action="login" method="post">
      <div class="form-group">
      <input name="userName" autofocus>
      </div>
      <div class="form-group">
      <input name="password" type="password" value="">
      </div>
      <input name="originUrl" style="display: none" value="${originUrl!''}" />
      <input type="submit" value="登录"/>
      </form>
  • sso-client

    client中比较简单,就一个Filter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    /**
    * @author Liu Hailin
    * @create 2017-10-20 下午4:19
    **/
    public class LoginFilter implements Filter {

    private final String USER_SESSION_KEY = "user_ses_key";

    private String ssoServerHost;

    private String ssoProtocol;

    private String ssoContextPath = "/sso/auth";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    ssoServerHost = filterConfig.getInitParameter( "sso-server-host" );
    ssoProtocol = filterConfig.getInitParameter( "sso-protocol" );
    if(StringUtils.isEmpty( ssoProtocol )){
    ssoProtocol = "http";
    }
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
    throws IOException, ServletException {
    if (servletRequest instanceof HttpServletRequest) {
    HttpServletRequest request = (HttpServletRequest)servletRequest;
    HttpServletResponse response = (HttpServletResponse)servletResponse;
    String authToken = request.getParameter( "token" );
    if(StringUtils.isEmpty( authToken )){
    User user = (User)request.getSession().getAttribute( "user_ses_key" );
    if (null == user) {
    redirctSSO( request, response );
    return;
    }
    }
    //TODO 去sso验证toke合法
    boolean isSafe = requestSSOValidateToken(authToken);
    if(!isSafe){
    redirctSSO( request, response );
    return;
    }
    }

    filterChain.doFilter( servletRequest, servletResponse );
    }

    private void redirctSSO(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String originUrl = request.getRequestURL().toString();
    response.sendRedirect(
    ssoProtocol + "://" + ssoServerHost + ssoContextPath + "?originUrl=" + originUrl );
    }

    //始终认为token是有效的,实际上应该去sso验证。
    //TODO
    private boolean requestSSOValidateToken(String authToken) {
    return true;
    }

    @Override
    public void destroy() {

    }
    }
  • biz-app-A和biz-app-B,这两个应用的配置一样

    • 入口

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      /**
      * @author Liu Hailin
      * @create 2017-10-20 下午3:10
      **/
      @SpringBootApplication
      public class BootBizApp_A {

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

      @Bean
      public FilterRegistrationBean filterRegistration(LoginFilter filter) {
      FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
      filterRegistrationBean.setFilter( filter );
      filterRegistrationBean.addUrlPatterns( "/*" );
      filterRegistrationBean.addInitParameter( "sso-server-host", "sso.com:9000" );
      filterRegistrationBean.setName( "loginFilter" );
      return filterRegistrationBean;
      }

      @Bean
      public LoginFilter loginFilter() {
      return new LoginFilter();
      }

      入口注册client中定义的Filter,拦截A系统下的请求。

    • 配置文件appliation.yml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      server:
      port: 8000
      session:
      timeout: 1 #设置session 1分钟

      spring:
      application:
      name: biz-app-A
      mvc:
      static-path-pattern: /static/**
      favicon:
      enabled: false
      servlet:
      load-on-startup: 1

    测试

    分别启动sso-server,A,B系统后,浏览器输入a.biz.com:8000/home会跳转到我们期望的sso系统。

    登录(随便输入),成功后便可以看到A系统下的页面。接着打开浏览器另一个标签页面,输入b.biz.com:8001/home发现不用登录就可以访问页面,反之亦然。

    总结

    上面我简单的验证了一下思路,没问题,里面验证token合法性还没有做,后面会补上。

    接下来我们要做

    • 补上token验证。
    • 优化扩展LoginFilter。
    • sso-server 中token的生成算法。(目前简单用的UUID)
    • 退出登录的实现。
坚持技术分享,您的支持将鼓励我继续创作!