0%

如图,是一种通用的用户权限模型。一般情况下会有5张表,分别是:用户表,角色表,权限表,用户角色关系表,角色权限对应表。

一般,资源分配时是基于角色的(即,资源访问权限赋给角色,用户通过角色进而拥有权限);而访问资源的时候是基于资源权限去进行授权判断的。

Spring Security和Apache Shiro是两个应用比较多的权限管理框架。Spring Security依赖Spring,其功能强大,相对于Shiro而言学习难度稍大一些。

Spring的强大是不言而喻的,可扩展性也很强,强大到用Spring家族的产品只要按照其推荐的做法来就非常非常简单,否则,自己去整合过程可能会很痛苦。

目前,我们项目是基于Spring Boot的,而且Spring Boot的权限管理也是推荐使用Spring Security的,所以再难也是要学习的。

Spring Security致力于为Java应用提供认证和授权管理。它是一个强大的,高度自定义的认证和访问控制框架。

具体介绍参见https://docs.spring.io/spring-security/site/docs/5.0.5.RELEASE/reference/htmlsingle/

这句话包括两个关键词:Authentication(认证)和 Authorization(授权,也叫访问控制)

认证是验证用户身份的合法性,而授权是控制你可以做什么。

简单地来说,认证就是你是谁,授权就是你可以做什么。

在开始集成之前,我们先简单了解几个接口:

AuthenticationProvider

AuthenticationProvider接口是用于认证的,可以通过实现这个接口来定制我们自己的认证逻辑,它的实现类有很多,默认的是JaasAuthenticationProvider

它的全称是 Java Authentication and Authorization Service (JAAS)

AccessDecisionManager

AccessDecisionManager是用于访问控制的,它决定用户是否可以访问某个资源,实现这个接口可以定制我们自己的授权逻辑。

AccessDecisionVoter

AccessDecisionVoter是投票器,在授权的时通过投票的方式来决定用户是否可以访问,这里涉及到投票规则。

UserDetailsService

UserDetailsService是用于加载特定用户信息的,它只有一个接口通过指定的用户名去查询用户。

UserDetails

UserDetails代表用户信息,即主体,相当于Shiro中的Subject。User是它的一个实现。

按照官方文档的说法,为了定义我们自己的认证管理,我们可以添加UserDetailsService, AuthenticationProvider, or AuthenticationManager这种类型的Bean。

实现的方式有多种,这里我选择最简单的一种(因为本身我们这里的认证授权也比较简单)

通过定义自己的UserDetailsService从数据库查询用户信息,至于认证的话就用默认的。

Maven依赖

复制代码

1
2 <project xmlns=“http://maven.apache.org/POM/4.0.0“ xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance
3 xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
4 <modelVersion>4.0.0</modelVersion>
5
6 <groupId>com.cjs.example</groupId>
7 <artifactId>cjs-springsecurity-example</artifactId>
8 <version>0.0.1-SNAPSHOT</version>
9 <packaging>jar</packaging>
10
11 <name>cjs-springsecurity-example</name>
12 <description></description>
13
14 <parent>
15 <groupId>org.springframework.boot</groupId>
16 <artifactId>spring-boot-starter-parent</artifactId>
17 <version>2.0.2.RELEASE</version>
18
19 </parent>
20
21 <properties>
22 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
23 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
24 <java.version>1.8</java.version>
25 </properties>
26
27 <dependencies>
28 <dependency>
29 <groupId>org.springframework.boot</groupId>
30 <artifactId>spring-boot-starter-cache</artifactId>
31 </dependency>
32 <dependency>
33 <groupId>org.springframework.boot</groupId>
34 <artifactId>spring-boot-starter-data-redis</artifactId>
35 </dependency>
36 <dependency>
37 <groupId>org.springframework.boot</groupId>
38 <artifactId>spring-boot-starter-security</artifactId>
39 </dependency>
40 <dependency>
41 <groupId>org.springframework.boot</groupId>
42 <artifactId>spring-boot-starter-thymeleaf</artifactId>
43 </dependency>
44 <dependency>
45 <groupId>org.springframework.boot</groupId>
46 <artifactId>spring-boot-starter-web</artifactId>
47 </dependency>
48 <dependency>
49 <groupId>org.thymeleaf.extras</groupId>
50 <artifactId>thymeleaf-extras-springsecurity4</artifactId>
51 <version>3.0.2.RELEASE</version>
52 </dependency>
53
54
55 <dependency>
56 <groupId>org.projectlombok</groupId>
57 <artifactId>lombok</artifactId>
58 <optional>true</optional>
59 </dependency>
60 <dependency>
61 <groupId>org.springframework.boot</groupId>
62 <artifactId>spring-boot-starter-test</artifactId>
63 <scope>test</scope>
64 </dependency>
65 <dependency>
66 <groupId>org.springframework.security</groupId>
67 <artifactId>spring-security-test</artifactId>
68 <scope>test</scope>
69 </dependency>
70 </dependencies>
71
72 <build>
73 <plugins>
74 <plugin>
75 <groupId>org.springframework.boot</groupId>
76 <artifactId>spring-boot-maven-plugin</artifactId>
77 </plugin>
78 </plugins>
79 </build>
80
81 </project>

复制代码

Security配置

复制代码

1 package com.cjs.example.config; 2
3 import com.cjs.example.support.MyUserDetailsService; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 9 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 13 import org.springframework.security.crypto.password.PasswordEncoder; 14
15 @Configuration 16 @EnableWebSecurity 17 @EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级别的权限认证
18 public class SecurityConfig extends WebSecurityConfigurerAdapter { 19
20 @Autowired 21 private MyUserDetailsService myUserDetailsService; 22
23
24 @Override 25 protected void configure(HttpSecurity http) throws Exception { 26 // 允许所有用户访问”/“和”/index.html”
27 http.authorizeRequests() 28 .antMatchers(“/“, “/index.html”).permitAll() 29 .anyRequest().authenticated() // 其他地址的访问均需验证权限
30 .and() 31 .formLogin() 32 .loginPage(“/login.html”) // 登录页
33 .failureUrl(“/login-error.html”).permitAll() 34 .and() 35 .logout() 36 .logoutSuccessUrl(“/index.html”); 37 } 38
39 @Override 40 protected void configure(AuthenticationManagerBuilder auth) throws Exception { 41 auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder()); 42 } 43
44 @Bean 45 public PasswordEncoder passwordEncoder() { 46 return new BCryptPasswordEncoder(); 47 } 48
49 }

复制代码

MyUserDetailsService

复制代码

1 package com.cjs.example.support; 2
3 import com.cjs.example.entity.SysPermission; 4 import com.cjs.example.entity.SysRole; 5 import com.cjs.example.entity.SysUser; 6 import com.cjs.example.service.UserService; 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.security.core.authority.SimpleGrantedAuthority; 9 import org.springframework.security.core.userdetails.User; 10 import org.springframework.security.core.userdetails.UserDetails; 11 import org.springframework.security.core.userdetails.UserDetailsService; 12 import org.springframework.security.core.userdetails.UsernameNotFoundException; 13 import org.springframework.stereotype.Service; 14
15 import java.util.ArrayList; 16 import java.util.List; 17
18 @Service 19 public class MyUserDetailsService implements UserDetailsService { 20
21 @Autowired 22 private UserService userService; 23
24 /**
25 * 授权的时候是对角色授权,而认证的时候应该基于资源,而不是角色,因为资源是不变的,而用户的角色是会变的 26 */
27
28 @Override 29 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 30 SysUser sysUser = userService.getUserByName(username); 31 if (null == sysUser) { 32 throw new UsernameNotFoundException(username); 33 } 34 List authorities = new ArrayList<>(); 35 for (SysRole role : sysUser.getRoleList()) { 36 for (SysPermission permission : role.getPermissionList()) { 37 authorities.add(new SimpleGrantedAuthority(permission.getCode())); 38 } 39 } 40
41 return new User(sysUser.getUsername(), sysUser.getPassword(), authorities); 42 } 43 }

复制代码

权限分配

复制代码

1 package com.cjs.example.service.impl; 2
3 import com.cjs.example.dao.UserDao; 4 import com.cjs.example.entity.SysUser; 5 import com.cjs.example.service.UserService; 6 import org.springframework.beans.factory.annotation.Autowired; 7 import org.springframework.cache.annotation.Cacheable; 8 import org.springframework.stereotype.Service; 9
10 @Service 11 public class UserServiceImpl implements UserService { 12
13 @Autowired 14 private UserDao userDao; 15
16 @Cacheable(cacheNames = “authority”, key = “#username”) 17 @Override 18 public SysUser getUserByName(String username) { 19 return userDao.selectByName(username); 20 } 21 }

复制代码

复制代码

1 package com.cjs.example.dao; 2
3 import com.cjs.example.entity.SysPermission; 4 import com.cjs.example.entity.SysRole; 5 import com.cjs.example.entity.SysUser; 6 import lombok.extern.slf4j.Slf4j; 7 import org.springframework.stereotype.Repository; 8
9 import java.util.Arrays; 10
11 @Slf4j 12 @Repository 13 public class UserDao { 14
15 private SysRole admin = new SysRole(“ADMIN”, “管理员”); 16 private SysRole developer = new SysRole(“DEVELOPER”, “开发者”); 17
18 { 19 SysPermission p1 = new SysPermission(); 20 p1.setCode(“UserIndex”); 21 p1.setName(“个人中心”); 22 p1.setUrl(“/user/index.html”); 23
24 SysPermission p2 = new SysPermission(); 25 p2.setCode(“BookList”); 26 p2.setName(“图书列表”); 27 p2.setUrl(“/book/list”); 28
29 SysPermission p3 = new SysPermission(); 30 p3.setCode(“BookAdd”); 31 p3.setName(“添加图书”); 32 p3.setUrl(“/book/add”); 33
34 SysPermission p4 = new SysPermission(); 35 p4.setCode(“BookDetail”); 36 p4.setName(“查看图书”); 37 p4.setUrl(“/book/detail”); 38
39 admin.setPermissionList(Arrays.asList(p1, p2, p3, p4)); 40 developer.setPermissionList(Arrays.asList(p1, p2)); 41
42 } 43
44 public SysUser selectByName(String username) { 45 log.info(“从数据库中查询用户”); 46 if (“zhangsan”.equals(username)) { 47 SysUser sysUser = new SysUser(“zhangsan”, “$2a$10$EIfFrWGINQzP.tmtdLd2hurtowwsIEQaPFR9iffw2uSKCOutHnQEm”); 48 sysUser.setRoleList(Arrays.asList(admin, developer)); 49 return sysUser; 50 }else if (“lisi”.equals(username)) { 51 SysUser sysUser = new SysUser(“lisi”, “$2a$10$EIfFrWGINQzP.tmtdLd2hurtowwsIEQaPFR9iffw2uSKCOutHnQEm”); 52 sysUser.setRoleList(Arrays.asList(developer)); 53 return sysUser; 54 } 55 return null; 56 } 57
58 }

复制代码

这里我设计的例子是用户登录成功以后跳到个人中心,然后用户可以可以进入图书列表查看。

用户zhangsan可以查看所有的,而lisi只能查看图书列表,不能添加不能查看详情。

页面设计

LoginController.java

复制代码

1 package com.cjs.example.controller; 2
3 import org.springframework.stereotype.Controller; 4 import org.springframework.ui.Model; 5 import org.springframework.web.bind.annotation.RequestMapping; 6
7 @Controller
8 public class LoginController { 9
10 // Login form
11 @RequestMapping(“/login.html”) 12 public String login() { 13 return “login.html”; 14 } 15
16 // Login form with error
17 @RequestMapping(“/login-error.html”) 18 public String loginError(Model model) { 19 model.addAttribute(“loginError”, true); 20 return “login.html”; 21 } 22
23 }

复制代码

BookController.java

复制代码

1 package com.cjs.example.controller; 2
3 import org.springframework.security.access.prepost.PreAuthorize; 4 import org.springframework.stereotype.Controller; 5 import org.springframework.web.bind.annotation.GetMapping; 6 import org.springframework.web.bind.annotation.RequestMapping; 7
8 @Controller
9 @RequestMapping(“/book”) 10 public class BookController { 11
12 @PreAuthorize(“hasAuthority(‘BookList’)”) 13 @GetMapping(“/list.html”) 14 public String list() { 15 return “book/list”; 16 } 17
18 @PreAuthorize(“hasAuthority(‘BookAdd’)”) 19 @GetMapping(“/add.html”) 20 public String add() { 21 return “book/add”; 22 } 23
24 @PreAuthorize(“hasAuthority(‘BookDetail’)”) 25 @GetMapping(“/detail.html”) 26 public String detail() { 27 return “book/detail”; 28 } 29 }

复制代码

UserController.java

复制代码

1 package com.cjs.example.controller; 2
3 import com.cjs.example.entity.SysUser; 4 import com.cjs.example.service.UserService; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.security.access.prepost.PreAuthorize; 7 import org.springframework.stereotype.Controller; 8 import org.springframework.web.bind.annotation.GetMapping; 9 import org.springframework.web.bind.annotation.RequestMapping; 10 import org.springframework.web.bind.annotation.ResponseBody; 11
12 @Controller 13 @RequestMapping(“/user”) 14 public class UserController { 15
16 @Autowired 17 private UserService userService; 18
19 /**
20 * 个人中心 21 */
22 @PreAuthorize(“hasAuthority(‘UserIndex’)”) 23 @GetMapping(“/index”) 24 public String index() { 25 return “user/index”; 26 } 27
28 @RequestMapping(“/hi”) 29 @ResponseBody 30 public String hi() { 31 SysUser sysUser = userService.getUserByName(“zhangsan”); 32 return sysUser.toString(); 33 } 34
35 }

复制代码

index.html

复制代码

1
2 <html lang=“en”>
3 <head>
4 <meta charset=“UTF-8”>
5 <title>首页</title>
6 </head>
7 <body>
8 <h2>这里是首页</h2>
9 </body>
10 </html>

复制代码

login.html

复制代码

1
2 <html lang=“zh” xmlns:th=“http://www.thymeleaf.org"\>
3 <head>
4 <meta charset=“UTF-8”>
5 <title>Login page</title>
6 </head>
7 <body>
8 <h1>Login page</h1>
9 <p th:if=“${loginError}” class=“error”>用户名或密码错误</p>
10 <form th:action=“@{/login.html}” method=“post”>
11 <label for=“username”>Username</label>: 12 <input type=“text” id=“username” name=“username” autofocus=“autofocus” />

13 <label for=“password”>Password</label>: 14 <input type=“password” id=“password” name=“password” />

15 <input type=“submit” value=“Login” />
16 </form>
17 </body>
18 </html>

复制代码

/user/index.html

复制代码

1
2 <html lang=“zh” xmlns:th=“http://www.thymeleaf.org"\>
3 <head>
4 <meta charset=“UTF-8”>
5 <title>个人中心</title>
6 </head>
7 <body>
8 <h2>个人中心</h2>
9 <div th:insert=“~{fragments/header::logout}”></div>
10 <a href=“/book/list.html”>图书列表</a>
11 </body>
12 </html>

复制代码

/book/list.html

复制代码

图书列表
图书列表