Spring Boot整合Spring Security的示例代码

时间:2022-12-02 16:08:15

本文讲述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

Spring Boot整合Spring Security的示例代码

点击登录后进入到“/hello”

Spring Boot整合Spring Security的示例代码

点击跳转到管理员页面

Spring Boot整合Spring Security的示例代码

在后台的“/admin”这个url对应的方法上面,限制了用户必须要拥有“user”角色。在数据库中也设置了登录的用户有这个角色。
现在我们修改数据库中的用户角色,改为“role_admin”。退出登录后重新登录,再次点击“前往管理员页面”按钮,会跳转到如下页面。

Spring Boot整合Spring Security的示例代码

因为现在没有了“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>

点击“前往管理员页面”按钮,在调试台可以看到如下

Spring Boot整合Spring Security的示例代码

这是因为框架内部防止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方式来提交,通过查看页面源代码可以看到

Spring Boot整合Spring Security的示例代码

框架在form表单中自动插入了一个隐藏域,value值就是那个token,所以使用form表单来提交post请求是可以直接通过的,而ajax方式提交的话,需要加上那段代码。

好了,这篇文章就讲到这,后面还会有文章讲述rest api风格如何来使用spring security来控制权限。

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

原文链接:https://blog.csdn.net/u283056051/article/details/55803855