好得很程序员自学网

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

详解Spring Boot Security

简介

spring security,这是一种基于 spring aop 和 servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 web 请求级和方法调用级处理身份确认和授权。

工作流程

从网上找了一张spring security 的工作流程图,如下。

图中标记的myxxx,就是我们项目中需要配置的。

快速上手

建表

表结构

建表语句

?

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

drop table if exists `user`;

drop table if exists `role`;

drop table if exists `user_role`;

drop table if exists `role_permission`;

drop table if exists `permission`;

create table `user` (

`id` bigint( 11 ) not null auto_increment,

`username` varchar( 255 ) not null ,

`password` varchar( 255 ) not null ,

primary key (`id`)

);

create table `role` (

`id` bigint( 11 ) not null auto_increment,

`name` varchar( 255 ) not null ,

primary key (`id`)

);

create table `user_role` (

`user_id` bigint( 11 ) not null ,

`role_id` bigint( 11 ) not null

);

create table `role_permission` (

`role_id` bigint( 11 ) not null ,

`permission_id` bigint( 11 ) not null

);

create table `permission` (

`id` bigint( 11 ) not null auto_increment,

`url` varchar( 255 ) not null ,

`name` varchar( 255 ) not null ,

`description` varchar( 255 ) null ,

`pid` bigint( 11 ) not null ,

primary key (`id`)

);

insert into user (id, username, password) values ( 1 , 'user' , 'e10adc3949ba59abbe56e057f20f883e' );

insert into user (id, username , password) values ( 2 , 'admin' , 'e10adc3949ba59abbe56e057f20f883e' );

insert into role (id, name) values ( 1 , 'user' );

insert into role (id, name) values ( 2 , 'admin' );

insert into permission (id, url, name, pid) values ( 1 , '/user/common' , 'common' , 0 );

insert into permission (id, url, name, pid) values ( 2 , '/user/admin' , 'admin' , 0 );

insert into user_role (user_id, role_id) values ( 1 , 1 );

insert into user_role (user_id, role_id) values ( 2 , 1 );

insert into user_role (user_id, role_id) values ( 2 , 2 );

insert into role_permission (role_id, permission_id) values ( 1 , 1 );

insert into role_permission (role_id, permission_id) values ( 2 , 1 );

insert into role_permission (role_id, permission_id) values ( 2 , 2 );

pom.xml

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<dependency>

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

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

</dependency>

<dependency>

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

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

</dependency>

<dependency>

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

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

</dependency>

<dependency>

  <groupid>org.thymeleaf.extras</groupid>

  <artifactid>thymeleaf-extras-security4</artifactid>

</dependency>

user

?

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

public class user implements userdetails , serializable {

  private long id;

  private string username;

  private string password;

  private list<role> authorities;

  public long getid() {

  return id;

  }

  public void setid( long id) {

  this .id = id;

  }

  @override

  public string getusername() {

  return username;

  }

  public void setusername(string username) {

  this .username = username;

  }

  @override

  public string getpassword() {

  return password;

  }

  public void setpassword(string password) {

  this .password = password;

  }

  @override

  public list<role> getauthorities() {

  return authorities;

  }

  public void setauthorities(list<role> authorities) {

  this .authorities = authorities;

  }

  /**

  * 用户账号是否过期

  */

  @override

  public boolean isaccountnonexpired() {

  return true ;

  }

  /**

  * 用户账号是否被锁定

  */

  @override

  public boolean isaccountnonlocked() {

  return true ;

  }

  /**

  * 用户密码是否过期

  */

  @override

  public boolean iscredentialsnonexpired() {

  return true ;

  }

  /**

  * 用户是否可用

  */

  @override

  public boolean isenabled() {

  return true ;

  }

}

上面的 user 类实现了 userdetails 接口,该接口是实现spring security 认证信息的核心接口。其中 getusername 方法为 userdetails 接口 的方法,这个方法返回 username,也可以是其他的用户信息,例如手机号、邮箱等。getauthorities() 方法返回的是该用户设置的权限信息,在本实例中,从数据库取出用户的所有角色信息,权限信息也可以是用户的其他信息,不一定是角色信息。另外需要读取密码,最后几个方法一般情况下都返回 true,也可以根据自己的需求进行业务判断。

role

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

public class role implements grantedauthority {

  private long id;

  private string name;

  public long getid() {

  return id;

  }

  public void setid( long id) {

  this .id = id;

  }

  public string getname() {

  return name;

  }

  public void setname(string name) {

  this .name = name;

  }

  @override

  public string getauthority() {

  return name;

  }

}

role 类实现了 grantedauthority 接口,并重写 getauthority() 方法。权限点可以为任何字符串,不一定是非要用角色名。

所有的authentication实现类都保存了一个grantedauthority列表,其表示用户所具有的权限。grantedauthority是通过authenticationmanager设置到authentication对象中的,然后accessdecisionmanager将从authentication中获取用户所具有的grantedauthority来鉴定用户是否具有访问对应资源的权限。

myuserdetailsservice

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@service

public class myuserdetailsservice implements userdetailsservice {

  @autowired

  private usermapper usermapper;

  @autowired

  private rolemapper rolemapper;

  @override

  public userdetails loaduserbyusername(string username) throws usernamenotfoundexception {

  //查数据库

  user user = usermapper.loaduserbyusername( username );

  if ( null != user) {

   list<role> roles = rolemapper.getrolesbyuserid( user.getid() );

   user.setauthorities( roles );

  }

  return user;

  }

}

service 层需要实现 userdetailsservice 接口,该接口是根据用户名获取该用户的所有信息, 包括用户信息和权限点。

myinvocationsecuritymetadatasourceservice

?

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

@component

public class myinvocationsecuritymetadatasourceservice implements filterinvocationsecuritymetadatasource {

 

  @autowired

  private permissionmapper permissionmapper;

 

  /**

  * 每一个资源所需要的角色 collection<configattribute>决策器会用到

  */

  private static hashmap<string, collection<configattribute>> map = null ;

 

 

  /**

  * 返回请求的资源需要的角色

  */

  @override

  public collection<configattribute> getattributes(object o) throws illegalargumentexception {

  if ( null == map) {

   loadresourcedefine();

  }

  //object 中包含用户请求的request 信息

  httpservletrequest request = ((filterinvocation) o).gethttprequest();

  for (iterator<string> it = map.keyset().iterator() ; it.hasnext();) {

   string url = it.next();

   if ( new antpathrequestmatcher( url ).matches( request )) {

   return map.get( url );

   }

  }

 

  return null ;

  }

 

  @override

  public collection<configattribute> getallconfigattributes() {

  return null ;

  }

 

  @override

  public boolean supports( class <?> aclass) {

  return true ;

  }

 

  /**

  * 初始化 所有资源 对应的角色

  */

  public void loadresourcedefine() {

  map = new hashmap<>( 16 );

  //权限资源 和 角色对应的表 也就是 角色权限 中间表

  list<rolepermisson> rolepermissons = permissionmapper.getrolepermissions();

 

  //某个资源 可以被哪些角色访问

  for (rolepermisson rolepermisson : rolepermissons) {

 

   string url = rolepermisson.geturl();

   string rolename = rolepermisson.getrolename();

   configattribute role = new securityconfig(rolename);

 

   if (map.containskey(url)){

   map.get(url).add(role);

   } else {

   list<configattribute> list = new arraylist<>();

   list.add( role );

   map.put( url , list );

   }

  }

  }

}

myinvocationsecuritymetadatasourceservice 类实现了 filterinvocationsecuritymetadatasource,filterinvocationsecuritymetadatasource 的作用是用来储存请求与权限的对应关系。

filterinvocationsecuritymetadatasource接口有3个方法:

boolean supports(class<?> clazz):指示该类是否能够为指定的方法调用或web请求提供configattributes。

collection

 getallconfigattributes():spring容器启动时自动调用, 一般把所有请求与权限的对应关系也要在这个方法里初始化, 保存在一个属性变量里。

collection

 getattributes(object object):当接收到一个http请求时, filtersecurityinterceptor会调用的方法. 参数object是一个包含url信息的httpservletrequest实例. 这个方法要返回请求该url所需要的所有权限集合。

myaccessdecisionmanager

?

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

/**

  * 决策器

  */

@component

public class myaccessdecisionmanager implements accessdecisionmanager {

 

  private final static logger logger = loggerfactory.getlogger(myaccessdecisionmanager. class );

 

  /**

  * 通过传递的参数来决定用户是否有访问对应受保护对象的权限

  *

  * @param authentication 包含了当前的用户信息,包括拥有的权限。这里的权限来源就是前面登录时userdetailsservice中设置的authorities。

  * @param object 就是filterinvocation对象,可以得到request等web资源

  * @param configattributes configattributes是本次访问需要的权限

  */

  @override

  public void decide(authentication authentication, object object, collection<configattribute> configattributes) throws accessdeniedexception, insufficientauthenticationexception {

  if ( null == configattributes || 0 >= configattributes.size()) {

   return ;

  } else {

   string needrole;

   for (iterator<configattribute> iter = configattributes.iterator(); iter.hasnext(); ) {

   needrole = iter.next().getattribute();

 

   for (grantedauthority ga : authentication.getauthorities()) {

    if (needrole.trim().equals(ga.getauthority().trim())) {

    return ;

    }

   }

   }

   throw new accessdeniedexception( "当前访问没有权限" );

  }

 

  }

 

  /**

  * 表示此accessdecisionmanager是否能够处理传递的configattribute呈现的授权请求

  */

  @override

  public boolean supports(configattribute configattribute) {

  return true ;

  }

 

  /**

  * 表示当前accessdecisionmanager实现是否能够为指定的安全对象(方法调用或web请求)提供访问控制决策

  */

  @override

  public boolean supports( class <?> aclass) {

  return true ;

  }}

myaccessdecisionmanager 类实现了accessdecisionmanager接口,accessdecisionmanager是由abstractsecurityinterceptor调用的,它负责鉴定用户是否有访问对应资源(方法或url)的权限。

myfiltersecurityinterceptor

?

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

@component

public class myfiltersecurityinterceptor extends abstractsecurityinterceptor implements filter {

  @autowired

  private filterinvocationsecuritymetadatasource securitymetadatasource;

  @autowired

  public void setmyaccessdecisionmanager(myaccessdecisionmanager myaccessdecisionmanager) {

  super .setaccessdecisionmanager(myaccessdecisionmanager);

  }

  @override

  public void dofilter(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception {

  filterinvocation fi = new filterinvocation(servletrequest, servletresponse, filterchain);

  invoke(fi);

  }

  public void invoke(filterinvocation fi) throws ioexception, servletexception {

  interceptorstatustoken token = super .beforeinvocation(fi);

  try {

   //执行下一个拦截器

   fi.getchain().dofilter(fi.getrequest(), fi.getresponse());

  } finally {

   super .afterinvocation(token, null );

  }

  }

  @override

  public class <?> getsecureobjectclass() {

  return filterinvocation. class ;

  }

  @override

  public securitymetadatasource obtainsecuritymetadatasource() {

  return this .securitymetadatasource;

  }

}

每种受支持的安全对象类型(方法调用或web请求)都有自己的拦截器类,它是abstractsecurityinterceptor的子类,abstractsecurityinterceptor 是一个实现了对受保护对象的访问进行拦截的抽象类。

abstractsecurityinterceptor的机制可以分为几个步骤:

1. 查找与当前请求关联的[配置属性(简单的理解就是权限)]
2. 将 安全对象(方法调用或web请求)、当前身份验证、配置属性 提交给决策器(accessdecisionmanager)
3. (可选)更改调用所根据的身份验证
4. 允许继续进行安全对象调用(假设授予了访问权)
5. 在调用返回之后,如果配置了afterinvocationmanager。如果调用引发异常,则不会调用afterinvocationmanager。

abstractsecurityinterceptor中的方法说明:

beforeinvocation()方法实现了对访问受保护对象的权限校验,内部用到了accessdecisionmanager和authenticationmanager;

finallyinvocation()方法用于实现受保护对象请求完毕后的一些清理工作,主要是如果在beforeinvocation()中改变了securitycontext,则在finallyinvocation()中需要将其恢复为原来的securitycontext,该方法的调用应当包含在子类请求受保护资源时的finally语句块中。

afterinvocation()方法实现了对返回结果的处理,在注入了afterinvocationmanager的情况下默认会调用其decide()方法。
了解了abstractsecurityinterceptor,就应该明白了,我们自定义myfiltersecurityinterceptor就是想使用我们之前自定义的 accessdecisionmanager 和 securitymetadatasource。

securityconfig

@enablewebsecurity注解以及websecurityconfigureradapter一起配合提供基于web的security。自定义类 继承了websecurityconfigureradapter来重写了一些方法来指定一些特定的web安全设置。

?

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

@configuration

@enablewebsecurity

public class securityconfig extends websecurityconfigureradapter {

  @autowired

  private myuserdetailsservice userservice;

  @autowired

  public void configureglobal(authenticationmanagerbuilder auth) throws exception {

   //校验用户

   auth.userdetailsservice( userservice ).passwordencoder( new passwordencoder() {

    //对密码进行加密

    @override

    public string encode(charsequence charsequence) {

     system.out.println(charsequence.tostring());

     return digestutils.md5digestashex(charsequence.tostring().getbytes());

    }

    //对密码进行判断匹配

    @override

    public boolean matches(charsequence charsequence, string s) {

     string encode = digestutils.md5digestashex(charsequence.tostring().getbytes());

     boolean res = s.equals( encode );

     return res;

    }

   } );

  }

  @override

  protected void configure(httpsecurity http) throws exception {

   http.authorizerequests()

     .antmatchers( "/" , "index" , "/login" , "/login-error" , "/401" , "/css/**" , "/js/**" ).permitall()

     .anyrequest().authenticated()

     .and()

     .formlogin().loginpage( "/login" ).failureurl( "/login-error" )

     .and()

     .exceptionhandling().accessdeniedpage( "/401" );

   http.logout().logoutsuccessurl( "/" );

  }

}

 

maincontroller

?

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

@controller

public class maincontroller {

  @requestmapping ( "/" )

  public string root() {

   return "redirect:/index" ;

  }

  @requestmapping ( "/index" )

  public string index() {

   return "index" ;

  }

  @requestmapping ( "/login" )

  public string login() {

   return "login" ;

  }

  @requestmapping ( "/login-error" )

  public string loginerror(model model) {

   model.addattribute( "loginerror" , true );

   return "login" ;

  }

  @getmapping ( "/401" )

  public string accessdenied() {

   return "401" ;

  }

  @getmapping ( "/user/common" )

  public string common() {

   return "user/common" ;

  }

  @getmapping ( "/user/admin" )

  public string admin() {

   return "user/admin" ;

  }

}

页面

login.html

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<!doctype html>

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

<head>

<head>

  <meta charset= "utf-8" >

  <title>首页</title>

</head>

<body>

  <h2>page list</h2>

  <a href= "/user/common" rel= "external nofollow" rel= "external nofollow" >common page</a>

  <br/>

  <a href= "/user/admin" rel= "external nofollow" rel= "external nofollow" >admin page</a>

  <br/>

  <form th:action= "@{/logout}" method= "post" >

   <input type= "submit" class = "btn btn-primary" value= "注销" />

  </form>

</body>

</html>

index.html

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<!doctype html>

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

<head>

<head>

  <meta charset= "utf-8" >

  <title>首页</title>

</head>

<body>

  <h2>page list</h2>

  <a href= "/user/common" rel= "external nofollow" rel= "external nofollow" >common page</a>

  <br/>

  <a href= "/user/admin" rel= "external nofollow" rel= "external nofollow" >admin page</a>

  <br/>

  <form th:action= "@{/logout}" method= "post" >

   <input type= "submit" class = "btn btn-primary" value= "注销" />

  </form>

</body>

</html>

admin.html

?

1

2

3

4

5

6

7

8

9

<!doctype html>

<head>

  <meta charset= "utf-8" >

  <title>admin page</title>

</head>

<body>

  success admin page!!!

</body>

</html>

common.html

?

1

2

3

4

5

6

7

8

9

<!doctype html>

<head>

  <meta charset= "utf-8" >

  <title>common page</title>

</head>

<body>

  success common page!!!

</body>

</html>

401.html

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

<!doctype html>

<html lang= "en" >

<head>

  <meta charset= "utf-8" >

  <title> 401 page</title>

</head>

<body>

  <div>

   <div>

    <h2>权限不够</h2>

    <p>拒绝访问!</p>

   </div>

  </div>

</body>

</html>

最后运行项目,可以分别用 user、admin 账号 去测试认证和授权是否正确。

总结

以上所述是小编给大家介绍的spring boot security,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!

查看更多关于详解Spring Boot Security的详细内容...

  阅读:12次