本文讲述spring boot整合spring security在方法上使用注解实现权限控制,使用自定义userdetailservice,从mysql中加载用户信息。使用security自带的md5加密,对用户密码进行加密。页面模板采用thymeleaf引擎。
源码地址:https://github.com/li5454yong/springboot-security.git
1、引入pom依赖
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
|
<parent>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-parent</artifactid>
<version> 1.4 . 4 .release</version>
</parent>
<dependencies>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-security</artifactid>
</dependency>
<dependency>
<groupid>org.springframework.security.oauth</groupid>
<artifactid>spring-security-oauth2</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-data-jpa</artifactid>
</dependency>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-jdbc</artifactid>
</dependency>
<dependency>
<groupid>mysql</groupid>
<artifactid>mysql-connector-java</artifactid>
<version> 5.1 . 34 </version>
</dependency>
<dependency>
<groupid>com.alibaba</groupid>
<artifactid>druid</artifactid>
<version> 1.0 . 15 </version>
</dependency>
</dependencies>
|
这里使用druid连接池,spring data jpa实现数据库访问。
2、配置spring security
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
|
@configuration
@enablewebmvcsecurity
@enableglobalmethodsecurity (prepostenabled = true ) //开启security注解
public class websecurityconfig extends websecurityconfigureradapter {
@bean
@override
protected authenticationmanager authenticationmanager() throws exception {
return super .authenticationmanager();
}
@override
protected void configure(httpsecurity http) throws exception {
//允许所有用户访问"/"和"/home"
http.authorizerequests()
.antmatchers( "/" , "/home" ).permitall()
//其他地址的访问均需验证权限
.anyrequest().authenticated()
.and()
.formlogin()
//指定登录页是"/login"
.loginpage( "/login" )
.defaultsuccessurl( "/hello" ) //登录成功后默认跳转到"/hello"
.permitall()
.and()
.logout()
.logoutsuccessurl( "/home" ) //退出登录后的默认url是"/home"
.permitall();
}
@autowired
public void configureglobal(authenticationmanagerbuilder auth) throws exception {
auth
.userdetailsservice(customuserdetailsservice())
.passwordencoder(passwordencoder());
}
/**
* 设置用户密码的加密方式为md5加密
* @return
*/
@bean
public md5passwordencoder passwordencoder() {
return new md5passwordencoder();
}
/**
* 自定义userdetailsservice,从数据库中读取用户信息
* @return
*/
@bean
public customuserdetailsservice customuserdetailsservice(){
return new customuserdetailsservice();
}
}
|
这里只做了基本的配置,设置了登录url、登录成功后跳转的url、退出后跳转到的url。使用@enableglobalmethodsecurity(prepostenabled = true)这个注解,可以开启security的注解,我们可以在需要控制权限的方法上面使用@preauthorize,@prefilter这些注解。
3、自定义userdetailservice
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
|
public class customuserdetailsservice implements userdetailsservice {
@autowired //数据库服务类
private suserservice suserservice;
@override
public userdetails loaduserbyusername(string username) throws usernamenotfoundexception {
//suser对应数据库中的用户表,是最终存储用户和密码的表,可自定义
//本例使用suser中的email作为用户名:
suser user = suserservice.finduserbyemail(username);
if (user == null ) {
throw new usernamenotfoundexception( "username " + username + " not found" );
}
// securityuser实现userdetails并将suser的email映射为username
securityuser securityuser = new securityuser(user);
collection<simplegrantedauthority> authorities = new arraylist<simplegrantedauthority>();
authorities.add( new simplegrantedauthority( "role_admin" ));
return securityuser;
}
}
|
这里只需要实现userdetailsservice 接口,重写loaduserbyusername方法,从数据库中取出用户信息。最后返回一个userdetails 实现类。
4、定义错误处理配置
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@configuration
public class errorpageconfig {
@bean
public embeddedservletcontainercustomizer embeddedservletcontainercustomizer(){
return new mycustomizer();
}
private static class mycustomizer implements embeddedservletcontainercustomizer {
@override
public void customize(configurableembeddedservletcontainer container) {
container.adderrorpages( new errorpage(httpstatus.forbidden, "/403" ));
}
}
}
|
访问发生错误时,跳转到”/403”.
5、controller接口
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
|
@controller
public class indexcontroller {
@resource
private suserservice suserservice;
@requestmapping ( "/home" )
public string home() {
return "home" ;
}
@preauthorize ( "hasrole('user')" )
@requestmapping (value = "/admin" ,method = requestmethod.get)
public string toadmin(){
return "helloadmin" ;
}
@requestmapping ( "/hello" )
public string hello() {
return "hello" ;
}
@requestmapping ( "/login" )
public string login(){
return "login" ;
}
@requestmapping ( "/" )
public string root() {
return "index" ;
}
@requestmapping ( "/403" )
public string error(){
return "403" ;
}
}
|
在toadmin()方法上面使用了@preauthorize(“hasrole(‘user')”),表示访问这个方法需要拥有user角色。如果希望控制到权限层面,可以使用@preauthorize(“haspermission()”)。这里只是用了其中的一个用法,更多的使用方法可以去看官方文档。需要注意的是,spring security默认的角色前缀是”role_”,使用hasrole方法时已经默认加上了,因此我们在数据库里面的用户角色应该是“role_user”,在user前面加上”role_”前缀。
6、测试
启动项目,访问http://localhost:1130/login
点击登录后进入到“/hello”
点击跳转到管理员页面
在后台的“/admin”这个url对应的方法上面,限制了用户必须要拥有“user”角色。在数据库中也设置了登录的用户有这个角色。
现在我们修改数据库中的用户角色,改为“role_admin”。退出登录后重新登录,再次点击“前往管理员页面”按钮,会跳转到如下页面。
因为现在没有了“user”权限,所以访问的时候抛出了异常,被拦截后重定向到了“/403”。
7、post方式访问,错误码403
首先,把“/admin”改为post请求
1
2
3
4
5
|
@preauthorize ( "hasrole('user')" )
@requestmapping (value = "/admin" ,method = requestmethod.post)
public string toadmin(){
return "helloadmin" ;
}
|
把“前往管理员页面”按钮的请求方式从原来的form表达get提交,改为ajax方式post提交。至于为什么不是用form的post提交后面再讲。先修改代码
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
|
<body>
<h1 th:inline= "text" >hello [[${#httpservletrequest.remoteuser}]]!</h1>
<!--<form th:action= "@{/logout}" method= "post" >
<input type= "submit" value= "sign out" />
</form>
<form th:action= "@{/admin}" method= "get" >
<input th:type= "submit" th:value= "前往管理员页面" />
</form>-->
<a th:href= "@{/admin}" rel= "external nofollow" >前往管理员用户页面</a>
<input th:type= "submit" onclick= "testpost()" th:value= "前往管理员页面" />
</body>
<script>
function testpost() {
$.ajax({
url: "/admin" ,
type: 'post' ,
success:function (data) {
}
});
}
</script>
|
点击“前往管理员页面”按钮,在调试台可以看到如下
这是因为框架内部防止csrf(cross-site request forgery跨站请求伪造)的发生,限制了除了get以外的大多数方法。
下面说解决办法:
首先在标签内添加如下内容。
1
2
|
<meta name= "_csrf" th:content= "${_csrf.token}" />
<meta name= "_csrf_hader" th:content= "${_csrf.headername}" />
|
只要添加这个token,后台就会验证这个token的正确性,如果正确,则接受post访问。
然后在ajax代码中添加以下代码:
1
2
3
4
5
|
var token = $( 'meta[name="_csrf"]' ).attr( "content" );
var header = $( 'meta[name="_csrf_hader"]' ).attr( "content" );
$(document).ajaxsend(function(e,xhr,opt){
xhr.setrequestheader(header,token);
});
|
这样就可以正常使用post、delete等其他方式来访问了。
上面说到使用表单的post方式来提交,通过查看页面源代码可以看到
框架在form表单中自动插入了一个隐藏域,value值就是那个token,所以使用form表单来提交post请求是可以直接通过的,而ajax方式提交的话,需要加上那段代码。
好了,这篇文章就讲到这,后面还会有文章讲述rest api风格如何来使用spring security来控制权限。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/u283056051/article/details/55803855