简介
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://www.thymeleaf.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://www.thymeleaf.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,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!