好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

SpringCloud实现SSO 单点登录的示例代码

前言

作为分布式项目, 单点登录 是必不可少的,文本基于之前的的博客(猛戳: springcloud系列——zuul 动态路由 , springboot系列——redis )记录zuul配合redis实现一个简单的sso单点登录实例

sso单点登录思路:

1、访问分布式系统的任意请求,被zuul的filter拦截过滤

2、在run方法里实现过滤规则:cookie有令牌accesstoken且作为key存在于redis,或者访问的是登录页面、登录请求则放行

3、否则,将重定向到sso-server的登录页面且原先的请求路径作为一个参数;response.sendredirect("http://localhost:10010/sso-server/sso/loginpage?url=" + url);

4、登录成功,sso-server生成accesstoken,并作为key(用户名+时间戳,这里只是demo,正常项目的令牌应该要更为复杂)存到redis,value值存用户id作为value(或者直接存储可暴露的部分用户信息也行)设置过期时间(我这里设置3分钟);设置cookie:new cookie("accesstoken",accesstoken);,设置maxage(60*3);、path("/");

5、sso-server单点登录服务负责校验用户信息、获取用户信息、操作redis缓存,提供接口,在eureka上注册

代码编写

sso-server

首先我们创建一个单点登录服务sso-server,并在eureka上注册(创建项目请参考之前的springcloud系列博客跟 springboot系列——redis )

login.html

我们这里需要用到页面,要先maven引入thymeleaf

?

1

2

3

4

<dependency>

   <groupid>org.springframework.boot</groupid>

   <artifactid>spring-boot-starter-thymeleaf</artifactid>

  </dependency>

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

<!doctype html>

<html xmlns:th= "http://HdhCmsTestthymeleaf.org" >

<head>

  <meta charset= "utf-8" >

  <title>登录页面</title>

</head>

<body>

  <form action= "/sso-server/sso/login" method= "post" >

   <input name= "url" type= "hidden" th:value= "${url}" />

   用户名:<input name= "username" type= "text" />

   密码:<input name= "password" type= "password" />

   <input value= "登录" type= "submit" />

  </form>

</body>

</html>

提供如下接口

?

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

66

67

68

69

70

71

72

73

74

75

76

@restcontroller

@enableeurekaclient

@springbootapplication

public class ssoserverapplication {

 

  public static void main(string[] args) {

   springapplication.run(ssoserverapplication. class , args);

  }

 

  @autowired

  private stringredistemplate template;

 

  /**

   * 判断key是否存在

   */

  @requestmapping ( "/redis/haskey/{key}" )

  public boolean haskey( @pathvariable ( "key" ) string key) {

   try {

    return template.haskey(key);

   } catch (exception e) {

    e.printstacktrace();

    return false ;

   }

  }

 

  /**

   * 校验用户名密码,成功则返回通行令牌(这里写死huanzi/123456)

   */

  @requestmapping ( "/sso/checkusernameandpassword" )

  private string checkusernameandpassword(string username, string password) {

   //通行令牌

   string flag = null ;

   if ( "huanzi" .equals(username) && "123456" .equals(password)) {

    //用户名+时间戳(这里只是demo,正常项目的令牌应该要更为复杂)

    flag = username + system.currenttimemillis();

    //令牌作为key,存用户id作为value(或者直接存储可暴露的部分用户信息也行)设置过期时间(我这里设置3分钟)

    template.opsforvalue().set(flag, "1" , ( long ) ( 3 * 60 ), timeunit.seconds);

   }

   return flag;

  }

 

  /**

   * 跳转登录页面

   */

  @requestmapping ( "/sso/loginpage" )

  private modelandview loginpage(string url) {

   modelandview modelandview = new modelandview( "login" );

   modelandview.addobject( "url" , url);

   return modelandview;

  }

 

  /**

   * 页面登录

   */

  @requestmapping ( "/sso/login" )

  private string login(httpservletresponse response, string username, string password, string url) {

   string check = checkusernameandpassword(username, password);

   if (!stringutils.isempty(check)) {

    try {

     cookie cookie = new cookie( "accesstoken" , check);

     cookie.setmaxage( 60 * 3 );

     //设置域

//    cookie.setdomain("huanzi.cn");

     //设置访问路径

     cookie.setpath( "/" );

     response.addcookie(cookie);

     //重定向到原先访问的页面

     response.sendredirect(url);

    } catch (ioexception e) {

     e.printstacktrace();

    }

    return null ;

   }

   return "登录失败" ;

  }

}

zuul-server

引入feign,用于调用sso-server服务

?

1

2

3

4

5

<!-- feign -->

  <dependency>

   <groupid>org.springframework.cloud</groupid>

   <artifactid>spring-cloud-starter-openfeign</artifactid>

  </dependency>

创建ssofeign.java接口

?

1

2

3

4

5

6

7

8

9

@feignclient (name = "sso-server" , path = "/" )

public interface ssofeign {

  /**

   * 判断key是否存在

   */

  @requestmapping ( "redis/haskey/{key}" )

  public boolean haskey( @pathvariable ( "key" ) string key);

 

}

启动类加入@enablefeignclients注解,否则启动会报错,无法注入ssofeign对象

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@enablezuulproxy

@enableeurekaclient

@enablefeignclients

@springbootapplication

public class zuulserverapplication {

 

  public static void main(string[] args) {

   springapplication.run(zuulserverapplication. class , args);

  }

 

  @bean

  public accessfilter accessfilter() {

   return new accessfilter();

  }

}

修改accessfilter过滤逻辑,注入feign接口,用于调用sso-server检查redis,修改run方法的过滤逻辑

?

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

66

67

68

69

70

71

72

73

74

/**

  * zuul过滤器,实现了路由检查

  */

public class accessfilter extends zuulfilter {

 

  @autowired

  private ssofeign ssofeign;

 

  /**

   * 通过int值来定义过滤器的执行顺序

   */

  @override

  public int filterorder() {

   // predecoration之前运行

   return pre_decoration_filter_order - 1 ;

  }

 

  /**

   * 过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型:

   * public static final string error_type = "error";

   * public static final string post_type = "post";

   * public static final string pre_type = "pre";

   * public static final string route_type = "route";

   */

  @override

  public string filtertype() {

   return pre_type;

  }

 

  /**

   * 过滤器的具体逻辑

   */

  @override

  public object run() {

   requestcontext ctx = requestcontext.getcurrentcontext();

   httpservletrequest request = ctx.getrequest();

   httpservletresponse response = ctx.getresponse();

 

   //访问路径

   string url = request.getrequesturl().tostring();

 

   //从cookie里面取值(zuul丢失cookie的解决方案:https://blog.csdn.net/lindan1984/article/details/79308396)

   string accesstoken = request.getparameter( "accesstoken" );

   for (cookie cookie : request.getcookies()) {

    if ( "accesstoken" .equals(cookie.getname())) {

     accesstoken = cookie.getvalue();

    }

   }

   //过滤规则:cookie有令牌且存在于redis,或者访问的是登录页面、登录请求则放行

   if (url.contains( "sso-server/sso/loginpage" ) || url.contains( "sso-server/sso/login" ) || (!stringutils.isempty(accesstoken) && ssofeign.haskey(accesstoken))) {

    ctx.setsendzuulresponse( true );

    ctx.setresponsestatuscode( 200 );

    return null ;

   } else {

    ctx.setsendzuulresponse( false );

    ctx.setresponsestatuscode( 401 );

    //重定向到登录页面

    try {

     response.sendredirect( "http://localhost:10010/sso-server/sso/loginpage?url=" + url);

    } catch (ioexception e) {

     e.printstacktrace();

    }

    return null ;

   }

  }

 

  /**

   * 返回一个boolean类型来判断该过滤器是否要执行

   */

  @override

  public boolean shouldfilter() {

   return true ;

  }

}

修改配置文件,映射sso-server代理路径,超时时间与丢失cookie的解决

?

1

2

3

4

5

6

7

8

zuul.routes.sso-server.path=/sso-server/**

zuul.routes.sso-server.service-id=sso-server

 

 

zuul.host.socket-timeout-millis= 60000

zuul.host.connect-timeout-millis= 10000

#zuul丢失cookie的解决方案:https: //blog.csdn.net/lindan1984/article/details/79308396

zuul.sensitive-headers=

测试效果

启动eureka、zuul-server、sso-server、config-server、myspringboot、springdatajpa(由两个应用组成,实现了ribbon负载均衡),记得启动我们的rabbitmq服务和redis服务!

刚开始,没有cookie且无redis的情况下,浏览器访问http://localhost:10010/myspringboot/feign/ribbon,被zuul-server拦截重定向到sso-server登录页面

开始登录校验,为了方便演示,我将密码的type改成text

登录失败,返回提示语

登录成功,重定向到之前的请求

cookie的值,以及过期时间

3分钟后我们再次访问http://localhost:10010/myspringboot/feign/ribbon,cookie、redis失效,需要从新登录

后记

sso单点登录就记录到这里,这里只是实现了单机版的sso,以后在进行升级吧。

问题报错:我们在sso-server设置cookie后,在zuul-server的run方法里获取不到设置的cookie,去浏览器查看,cookie没有设置成功,zuul丢失cookie

解决方案

我们是使用spring cloud zuul作为api-gateway实践中,发现默认zuul会过滤掉cookie等header信息,有些业务场景需要传递这些信息该怎么处理呢?

处理方式   在api-gateway的application.properties文件中添加 zuul.sensitive-headers=  

问题原因

负责根据serviceid来路由的ribbonroutingfilter在route之前会调用proxyrequesthelper的buildzuulrequestheaders(request)来重新组装一个新的header。

在buildzuulrequestheaders方法中会对requsetheader中的每一项调用isincludedheader(name)来判断当前项是否应该留在新的header中,如下图,如果当前项在ignored_headers(需要忽略的信息)中,就不会在新header中保留。

predecorationfilter过滤器会调用proxyrequesthelper的addignoredheaders方法把敏感信息(zuulproperties的sensitiveheaders属性)添加到请求上下文的ignored_headers中

sensitiveheaders的默认值初始值是"cookie", "set-cookie", "authorization"这三项,可以看到cookie被列为了敏感信息,所以不会放到新header中

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

原文链接:http://HdhCmsTestcnblogs测试数据/huanzi-qch/p/10249227.html

查看更多关于SpringCloud实现SSO 单点登录的示例代码的详细内容...

  阅读:16次