Hello大家好,本章我们添加shiro权限保护接口功能 。有问题可以联系我mr_beany@163.com。另求各路大神指点,感谢
一:什么是shiro
Shiro是一个Java平台的开源权限框架,用于认证和访问授权。具体来说,满足对如下元素的支持:
- 用户,角色,权限(仅仅是操作权限,数据权限必须与业务需求紧密结合),资源(url)。
- 用户分配角色,角色定义权限。
- 访问授权时支持角色或者权限,并且支持多级的权限定义。
简单来说shiro是通过自己的配置,来保证接口只能被指定的角色或权限访问,保证了接口的安全。
二:添加shiro依赖
1
2
3
4
5
6 1<dependency>
2 <groupId>org.apache.shiro</groupId>
3 <artifactId>shiro-spring-boot-web-starter</artifactId>
4 <version>1.4.0-RC2</version>
5</dependency>复制代码
6
三:创建权限,角色,用户角色权限关系表等数据库表
1:修改原有userInfo表
添加password(用户密码,经过加密之后的),salt(加密盐值)
修改之后结构为下图
其中password数据为123456加密之后生成的
2:添加角色表
1
2
3
4
5
6
7
8
9
10
11 1CREATE TABLE `sys_role` (
2 `id` varchar(36) NOT NULL COMMENT '角色名称',
3 `role_name` varchar(255) DEFAULT NULL COMMENT '角色名称,用于显示',
4 `role_desc` varchar(255) DEFAULT NULL COMMENT '角色描述',
5 `role_value` varchar(255) DEFAULT NULL COMMENT '角色值,用于权限判断',
6 `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
7 `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
8 `is_disable` int(1) DEFAULT NULL COMMENT '是否禁用',
9 PRIMARY KEY (`id`)
10) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';复制代码
11
3:添加用户角色关系表
1
2
3
4
5
6
7
8 1CREATE TABLE `user_role` (
2 `id` varchar(36) NOT NULL,
3 `user_id` varchar(36) DEFAULT NULL COMMENT '用户ID',
4 `role_id` varchar(36) DEFAULT NULL COMMENT '角色id',
5 PRIMARY KEY (`id`)
6) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户角色关系表';
7复制代码
8
4:添加权限表
1
2
3
4
5
6
7
8
9
10
11 1CREATE TABLE `sys_perm` (
2 `id` varchar(36) NOT NULL,
3 `perm_name` varchar(255) DEFAULT NULL COMMENT '权限名称',
4 `perm_desc` varchar(255) DEFAULT NULL COMMENT '权限描述',
5 `perm_value` varchar(255) DEFAULT NULL COMMENT '权限值',
6 `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
7 `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
8 `is_disable` int(1) DEFAULT NULL COMMENT '是否禁用',
9 PRIMARY KEY (`id`)
10) ENGINE=InnoDB DEFAULT CHARSET=utf8;复制代码
11
5:添加角色权限表
1
2
3
4
5
6
7 1CREATE TABLE `role_perm` (
2 `id` varchar(36) NOT NULL,
3 `perm_id` varchar(32) DEFAULT NULL COMMENT '权限id',
4 `role_id` varchar(32) DEFAULT NULL COMMENT '角色id',
5 PRIMARY KEY (`id`)
6) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色权限表';复制代码
7
表关系大致为下图,一个用户对应多个角色,一个角色对用多个权限
原谅我辣鸡的画图
四:利用代码生成器生成四张新表的mapper,dao,service和controller
大家直接生成就可以,由于代码过多,这里不做展示,详情可以去码云上参考
五:添加查询角色和权限的方法
UserRoleMapper.xml
1
2
3
4
5
6
7
8
9 1<!--根据用户id查询该用户所有角色-->
2<select id="getRolesByUserId" resultType="string" parameterType="string">
3 select sr.role_value
4 from user_role ur
5 left join sys_role sr on ur.role_id = sr.id
6 where ur.user_id = #{userId,jdbcType=VARCHAR}
7 and sr.is_disable = 0
8</select>复制代码
9
UserRoleMapper.java
1
2 1List<String> getRolesByUserId(String userId);复制代码
2
RolePermMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1<select id="getPermsByUserId" resultType="string" parameterType="string">
2 select distinct
3 p.perm_value
4 from
5 sys_perm p,
6 role_perm rp,
7 user_role ur
8 where
9 p.id = rp.perm_id
10 and ur.role_id = rp.role_id
11 and ur.user_id = #{userId,jdbcType=VARCHAR}
12 and p.is_disable = 0
13</select>复制代码
14
说明,这里查询出该用户对应的所有权限,由于角色与权限是多对多的关系,所以查询出的用户的权限可能会有重复,需要distinct去重。
RolePermMapper.java
1
2 1List<String> getPermsByUserId(String userId);复制代码
2
修改userInfo实体类为如下
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 1package com.example.demo.model;
2
3import javax.persistence.Column;
4import javax.persistence.Id;
5import javax.persistence.Transient;
6import java.util.HashSet;
7import java.util.Set;
8
9/**
10 * @author 张瑶
11 * @Description:
12 * @time 2018/4/18 11:55
13 */
14public class UserInfo {
15
16 /**
17 * 主键
18 */
19 @Id
20 private String id;
21
22 /**
23 * 用户名
24 */
25 @Column(name = "user_name")
26 private String userName;
27
28 private String password;
29
30 /**
31 * 加密盐值
32 */
33 private String salt;
34
35 /**
36 * 用户所有角色值,用于shiro做角色权限的判断
37 */
38 @Transient
39 private Set<String> roles;
40
41 /**
42 * 用户所有权限值,用于shiro做资源权限的判断
43 */
44 @Transient
45 private Set<String> perms;
46
47
48
49 public String getId() {
50 return id;
51 }
52
53 public void setId(String id) {
54 this.id = id;
55 }
56
57 public String getUserName() {
58 return userName;
59 }
60
61 public void setUserName(String userName) {
62 this.userName = userName;
63 }
64
65 public String getPassword() {
66 return password;
67 }
68
69 public void setPassword(String password) {
70 this.password = password;
71 }
72
73 public Set<String> getRoles() {
74 return roles;
75 }
76
77 public void setRoles(Set<String> roles) {
78 this.roles = roles;
79 }
80
81 public Set<String> getPerms() {
82 return perms;
83 }
84
85 public void setPerms(Set<String> perms) {
86 this.perms = perms;
87 }
88
89 public String getSalt() {
90 return salt;
91 }
92
93 public void setSalt(String salt) {
94 this.salt = salt;
95 }
96}
97复制代码
98
六:自定义Realm
创建core→shiro→CustomRealm.java
1:登录认证实现
在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.
Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
该方法主要执行以下操作:
- 检查提交的进行认证的令牌信息
- 根据令牌信息从数据源(通常为数据库)中获取用户信息
- 对用户信息进行匹配验证。
- 验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
- 验证失败则抛出AuthenticationException异常信息。
而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。
2:链接权限的实现
重载doGetAuthorizationInfo(),来定义如何获取用户的角色和权限的逻辑,给shiro做权限判断
完整代码如下
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93 1package com.example.demo.core.shiro;
2
3
4import com.example.demo.model.UserInfo;
5import com.example.demo.service.RolePermService;
6import com.example.demo.service.UserInfoService;
7import com.example.demo.service.UserRoleService;
8import org.apache.shiro.authc.*;
9import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
10import org.apache.shiro.authz.AuthorizationException;
11import org.apache.shiro.authz.AuthorizationInfo;
12import org.apache.shiro.authz.SimpleAuthorizationInfo;
13import org.apache.shiro.realm.AuthorizingRealm;
14import org.apache.shiro.subject.PrincipalCollection;
15import org.apache.shiro.util.ByteSource;
16import org.springframework.beans.factory.annotation.Autowired;
17
18import java.util.HashSet;
19import java.util.List;
20import java.util.Set;
21
22/**
23 * 自定义如何查询用户信息,如何查询用户的角色和权限,如何校验密码等逻辑
24 */
25public class CustomRealm extends AuthorizingRealm {
26
27 @Autowired
28 private UserInfoService userService;
29 @Autowired
30 private UserRoleService userRoleService;
31 @Autowired
32 private RolePermService rolePermService;
33
34 /**
35 * 告诉shiro如何根据获取到的用户信息中的密码和盐值来校验密码
36 */
37 {
38 //设置用于匹配密码的CredentialsMatcher
39 HashedCredentialsMatcher hashMatcher = new HashedCredentialsMatcher();
40 hashMatcher.setHashAlgorithmName("md5");
41 hashMatcher.setStoredCredentialsHexEncoded(true);
42 //加密的次数
43 hashMatcher.setHashIterations(1024);
44 this.setCredentialsMatcher(hashMatcher);
45 }
46
47
48 /**
49 * 定义如何获取用户的角色和权限的逻辑,给shiro做权限判断
50 */
51 @Override
52 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
53 if (principals == null) {
54 throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
55 }
56 UserInfo user = (UserInfo) getAvailablePrincipal(principals);
57 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
58 info.setRoles(user.getRoles());
59 info.setStringPermissions(user.getPerms());
60 return info;
61 }
62
63 /**
64 * 定义如何获取用户信息的业务逻辑,给shiro做登录
65 */
66 @Override
67 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
68 UsernamePasswordToken upToken = (UsernamePasswordToken) token;
69 String username = upToken.getUsername();
70 if (username == null) {
71 throw new AccountException("Null usernames are not allowed by this realm.");
72 }
73 UserInfo userDB = userService.selectBy("userName",username);
74 if (userDB == null) {
75 throw new UnknownAccountException("No account found for admin [" + username + "]");
76 }
77 //查询用户的角色和权限存到SimpleAuthenticationInfo中,这样在其它地方
78 //SecurityUtils.getSubject().getPrincipal()就能拿出用户的所有信息,包括角色和权限
79 List<String> roleList = userRoleService.getRolesByUserId(userDB.getId());
80 List<String> permList = rolePermService.getPermsByUserId(userDB.getId());
81 Set<String> roles = new HashSet(roleList);
82 Set<String> perms = new HashSet(permList);
83 userDB.setRoles(roles);
84 userDB.setPerms(perms);
85
86 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userDB, userDB.getPassword(), getName());
87 info.setCredentialsSalt(ByteSource.Util.bytes(userDB.getSalt()));
88 return info;
89
90 }
91
92}复制代码
93
七:添加shiro配置
创建core→configurer→ShiroConfigurer
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 1package com.example.demo.core.configurer;
2
3import com.example.demo.core.shiro.CustomRealm;
4import org.apache.shiro.realm.Realm;
5import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
6import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
7import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
8import org.springframework.context.annotation.Bean;
9import org.springframework.context.annotation.Configuration;
10
11import javax.annotation.Resource;
12
13@Configuration
14public class ShiroConfigurer {
15
16 /**
17 * 注入自定义的realm,告诉shiro如何获取用户信息来做登录或权限控制
18 */
19 @Bean
20 public Realm realm() {
21 return new CustomRealm();
22 }
23
24 @Bean
25 public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
26 DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
27 /**
28 * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
29 * 在@Controller注解的类的方法中加入@RequiresRole注解,会导致该方法无法映射请求,导致返回404。
30 * 加入这项配置能解决这个bug
31 */
32 creator.setUsePrefix(true);
33 return creator;
34 }
35
36 /**
37 * 这里统一做鉴权,即判断哪些请求路径需要用户登录,哪些请求路径不需要用户登录
38 * @return
39 */
40 @Bean
41 public ShiroFilterChainDefinition shiroFilterChainDefinition() {
42 DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
43 chain.addPathDefinition( "/userInfo/selectById", "authc, roles[admin]");
44 chain.addPathDefinition( "/logout", "anon");
45 chain.addPathDefinition( "/userInfo/selectAll", "anon");
46 chain.addPathDefinition( "/userInfo/login", "anon");
47 chain.addPathDefinition( "/**", "authc");
48 return chain;
49 }
50}
51复制代码
52
shiro提供和多个默认的过滤器,我们可以用这些过滤器来配置控制指定url的权限:
anon
AnonymousFilter
指定url可以匿名访问
authc
FormAuthenticationFilter
指定url需要form表单登录,默认会从请求中获取username、password,rememberMe等参数并尝试登录,如果登录不了就会跳转到loginUrl配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以定制。
authcBasic
BasicHttpAuthenticationFilter
指定url需要basic登录
logout
LogoutFilter
登出过滤器,配置指定url就可以实现退出功能,非常方便
noSessionCreation
NoSessionCreationFilter
禁止创建会话
perms
PermissionsAuthorizationFilter
需要指定权限才能访问
port
PortFilter
需要指定端口才能访问
rest
HttpMethodPermissionFilter
将http请求方法转化成相应的动词来构造一个权限字符串
roles
RolesAuthorizationFilter
需要指定角色才能访问
ssl
SslFilter
需要https请求才能访问
user
UserFilter
需要已登录或“记住我”的用户才能访问
八:在UserInfoController中添加登录方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1@PostMapping("/login")
2public RetResult<UserInfo> login(String userName, String password) {
3 Subject currentUser = SecurityUtils.getSubject();
4 //登录
5 try {
6 currentUser.login(new UsernamePasswordToken(userName, password));
7 }catch (IncorrectCredentialsException i){
8 throw new ServiceException("密码输入错误");
9 }
10 //从session取出用户信息
11 UserInfo user = (UserInfo) currentUser.getPrincipal();
12 return RetResponse.makeOKRsp(user);
13}复制代码
14
九:数据库添加权限数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1INSERT INTO `sys_role` VALUES ('1', '财务', '负责发工资', 'cw', '2018-05-26 00:37:52', null, '0');
2INSERT INTO `sys_role` VALUES ('2', '人事', '负责员工', 'rs', '2018-05-26 00:38:18', null, '0');
3INSERT INTO `user_role` VALUES ('1', '1', '1');
4INSERT INTO `user_role` VALUES ('2', '1', '2');
5INSERT INTO `sys_perm` VALUES ('1', '创建', '创建权限', 'create', '2018-05-26 00:39:16', null, '0');
6INSERT INTO `sys_perm` VALUES ('2', '删除', '删除权限', 'delete', '2018-05-26 00:39:39', null, '0');
7INSERT INTO `sys_perm` VALUES ('3', '修改', '修改权限', 'update', '2018-05-26 00:39:58', null, '0');
8INSERT INTO `sys_perm` VALUES ('4', '查询', '查询权限', 'select', '2018-05-26 00:40:16', null, '0');
9INSERT INTO `role_perm` VALUES ('1', '1', '1');
10INSERT INTO `role_perm` VALUES ('2', '2', '1');
11INSERT INTO `role_perm` VALUES ('3', '1', '2');
12INSERT INTO `role_perm` VALUES ('4', '2', '2');
13INSERT INTO `role_perm` VALUES ('5', '3', '2');
14INSERT INTO `role_perm` VALUES ('6', '4', '2');复制代码
15
十:添加shiroUtilsController
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 1package com.example.demo.controller;
2
3import com.example.demo.core.ret.ServiceException;
4import com.example.demo.model.UserInfo;
5import org.apache.shiro.SecurityUtils;
6import org.apache.shiro.authz.UnauthenticatedException;
7import org.apache.shiro.authz.UnauthorizedException;
8import org.springframework.web.bind.annotation.GetMapping;
9import org.springframework.web.bind.annotation.PostMapping;
10import org.springframework.web.bind.annotation.RequestMapping;
11import org.springframework.web.bind.annotation.RestController;
12
13
14@RestController
15@RequestMapping("shiroUtils")
16public class ShiroUtilsController {
17
18 @GetMapping("/noLogin")
19 public void noLogin() {
20 throw new UnauthenticatedException();
21 }
22
23 @GetMapping("/noAuthorize")
24 public void noAuthorize() {
25 throw new UnauthorizedException();
26 }
27
28
29 @PostMapping("/getNowUser")
30 public UserInfo getNowUser() {
31 UserInfo u = (UserInfo) SecurityUtils.getSubject().getPrincipal();
32 return u;
33 }
34
35}复制代码
36
十一:添加错误异常码
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 1package com.example.demo.core.ret;
2
3/**
4 * @Description: 响应码枚举,参考HTTP状态码的语义
5 * @author 张瑶
6 * @date 2018/4/19 09:42
7 */
8public enum RetCode {
9
10 // 成功
11 SUCCESS(200),
12
13 // 失败
14 FAIL(400),
15
16 // 未认证(签名错误)
17 UNAUTHORIZED(401),
18
19 /** 未登录 */
20 UNAUTHEN(4401),
21
22 /** 未授权,拒绝访问 */
23 UNAUTHZ(4403),
24
25 // 服务器内部错误
26 INTERNAL_SERVER_ERROR(500);
27
28 public int code;
29
30 RetCode(int code) {
31 this.code = code;
32 }
33}复制代码
34
十二:添加异常拦截
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 1@Override
2 public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
3 exceptionResolvers.add(new HandlerExceptionResolver() {
4 @Override
5 public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
6 RetResult<Object> result = new RetResult<Object>();
7 // 业务失败的异常,如“账号或密码错误”
8 if (e instanceof ServiceException) {
9 result.setCode(RetCode.FAIL).setMsg(e.getMessage()).setData(null);
10 LOGGER.info(e.getMessage());
11 } else if (e instanceof NoHandlerFoundException) {
12 result.setCode(RetCode.NOT_FOUND).setMsg("接口 [" + request.getRequestURI() + "] 不存在");
13 } else if (e instanceof UnauthorizedException) {
14 result.setCode(RetCode.UNAUTHEN).setMsg("用户没有访问权限").setData(null);
15 }else if (e instanceof UnauthenticatedException) {
16 result.setCode(RetCode.UNAUTHEN).setMsg("用户未登录").setData(null);
17 }else if (e instanceof ServletException) {
18 result.setCode(RetCode.FAIL).setMsg(e.getMessage());
19 } else {
20 result.setCode(RetCode.INTERNAL_SERVER_ERROR).setMsg("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
21 String message;
22 if (handler instanceof HandlerMethod) {
23 HandlerMethod handlerMethod = (HandlerMethod) handler;
24 message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s", request.getRequestURI(), handlerMethod.getBean().getClass().getName(), handlerMethod.getMethod()
25 .getName(), e.getMessage());
26 } else {
27 message = e.getMessage();
28 }
29 LOGGER.error(message, e);
30 }
31 responseResult(response, result);
32 return new ModelAndView();
33 }
34 });
35 }复制代码
36
十三:添加shiro路径配置
在application.properties中添加
1
2
3
4
5
6 1#shiro配置
2#用户未登录
3shiro.loginUrl=/shiroUtils/noLogin
4#用户没有权限
5shiro.unauthorizedUrl=/shiroUtils/noAuthorize复制代码
6
十四:测试
输入localhost:8080/userInfo/selectAll
我们可以看到可以拿到数据
输入localhost:8080/userInfo/selectById
然后登陆
再次访问localhost:8080/userInfo/selectById,我们可以看到shiro已经生效
修改用户访问权限,重新启动,重新登陆
1
2 1chain.addPathDefinition( "/userInfo/selectById", "authc, roles[cw]");复制代码
2
再次访问localhost:8080/userInfo/selectById
十五:优化
优化一:权限不可能一直不变,当我们需要修改权限时,我们需要手动修改shiro配置文件,显然是不合理的,最好的办法是把权限存储在数据库中,修改时修改数据库
1:创建url资源表并添加数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1CREATE TABLE `sys_permission_init` (
2 `id` varchar(255) NOT NULL,
3 `url` varchar(255) DEFAULT NULL COMMENT '程序对应url地址',
4 `permission_init` varchar(255) DEFAULT NULL COMMENT '对应shiro权限',
5 `sort` int(100) DEFAULT NULL COMMENT '排序',
6 PRIMARY KEY (`id`)
7) ENGINE=InnoDB DEFAULT CHARSET=utf8;
8
9INSERT INTO `sys_permission_init` VALUES ('1', '/userInfo/login', 'anon', '1');
10INSERT INTO `sys_permission_init` VALUES ('2', '/userInfo/selectAll', 'anon', '2');
11INSERT INTO `sys_permission_init` VALUES ('3', '/logout', 'anon', '3');
12INSERT INTO `sys_permission_init` VALUES ('4', '/**', 'authc', '0');
13INSERT INTO `sys_permission_init` VALUES ('5', '/userInfo/selectAlla', 'authc, roles[admin]', '6');
14INSERT INTO `sys_permission_init` VALUES ('6', '/sysPermissionInit/aaa', 'anon', '5');复制代码
15
这里我们需要让'/**'始终在最后,所以需要添加sort进行排序
2:根据工具生成mapper,dao,service,controller并添加我们需要的查询方法
SysPermissionInitMapper.xml
1
2
3
4
5
6
7
8
9
10
11 1<sql id="Base_Column_List">
2 id, url, permission_init, sort
3</sql>
4
5<select id="selectAllOrderBySort" resultMap="BaseResultMap">
6 SELECT
7 <include refid="Base_Column_List"/>
8 from sys_permission_init
9 order by sort desc
10</select>复制代码
11
SysPermissionInitMapper.java
1
2 1List<SysPermissionInit> selectAllOrderBySort();复制代码
2
3:修改
ShiroConfigurer.java
修改后如下
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 1package com.example.demo.core.configurer;
2
3import com.example.demo.core.shiro.CustomRealm;
4import com.example.demo.model.SysPermissionInit;
5import com.example.demo.service.SysPermissionInitService;
6import org.apache.shiro.realm.Realm;
7import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
8import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
9import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
10import org.springframework.context.annotation.Bean;
11import org.springframework.context.annotation.Configuration;
12
13import javax.annotation.Resource;
14import java.util.List;
15
16@Configuration
17public class ShiroConfigurer {
18
19 @Resource
20 private SysPermissionInitService sysPermissionInitService;
21
22 /**
23 * 注入自定义的realm,告诉shiro如何获取用户信息来做登录或权限控制
24 */
25 @Bean
26 public Realm realm() {
27 return new CustomRealm();
28 }
29
30 @Bean
31 public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
32 DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
33 /**
34 * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
35 * 在@Controller注解的类的方法中加入@RequiresRole注解,会导致该方法无法映射请求,导致返回404。
36 * 加入这项配置能解决这个bug
37 */
38 creator.setUsePrefix(true);
39 return creator;
40 }
41
42 /**
43 * 这里统一做鉴权,即判断哪些请求路径需要用户登录,哪些请求路径不需要用户登录
44 * @return
45 */
46 @Bean
47 public ShiroFilterChainDefinition shiroFilterChainDefinition() {
48 DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
49 List<SysPermissionInit> list = sysPermissionInitService.selectAllOrderBySort();
50 for(int i = 0,length = list.size();i<length;i++){
51 SysPermissionInit sysPermissionInit = list.get(i);
52 chain.addPathDefinition(sysPermissionInit.getUrl(), sysPermissionInit.getPermissionInit());
53 }
54 return chain;
55 }
56}复制代码
57
4:测试
输入localhost:8080/userInfo/selectById
然后登陆
再次访问localhost:8080/userInfo/selectById,我们可以看到shiro已经生效
修改数据库用户访问权限,重新启动,重新登陆
1
2
3
4
5 1UPDATE sys_permission_init
2SET permission_init = 'authc, roles[cw]'
3WHERE
4 id = 5复制代码
5
再次访问localhost:8080/userInfo/selectById
优化二:每次当我们修改权限信息时,都需要重启服务器,这显然也是不合理的
1:创建shiroService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1package com.example.demo.service;
2
3import java.util.Map;
4
5/**
6 * shiro 动态更新权限
7 */
8public interface ShiroService {
9
10 Map<String, String> loadFilterChainDefinitions();
11
12 /**
13 * 动态修改权限
14 */
15 void updatePermission();
16}复制代码
17
2:创建shiroServiceImpl
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
68
69
70
71
72
73
74 1package com.example.demo.service.impl;
2
3import com.example.demo.model.SysPermissionInit;
4import com.example.demo.service.ShiroService;
5import com.example.demo.service.SysPermissionInitService;
6import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
7import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
8import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
9import org.apache.shiro.web.servlet.AbstractShiroFilter;
10import org.springframework.beans.factory.annotation.Autowired;
11import org.springframework.stereotype.Service;
12
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
16
17@Service
18public class ShiroServiceImpl implements ShiroService {
19
20 @Autowired
21 ShiroFilterFactoryBean shiroFilterFactoryBean;
22
23 @Autowired
24 SysPermissionInitService sysPermissionInitService;
25
26 /**
27 * 初始化权限
28 */
29 @Override
30 public Map<String, String> loadFilterChainDefinitions() {
31 // 权限控制map.从数据库获取
32 Map<String, String> filterChainDefinitionMap = new HashMap<>();
33 List<SysPermissionInit> list = sysPermissionInitService.selectAllOrderBySort();
34 for (SysPermissionInit sysPermissionInit : list) {
35 filterChainDefinitionMap.put(sysPermissionInit.getUrl(),
36 sysPermissionInit.getPermissionInit());
37 }
38 return filterChainDefinitionMap;
39 }
40
41
42 /**
43 * 重新加载权限
44 */
45 @Override
46 public void updatePermission() {
47 synchronized (shiroFilterFactoryBean) {
48 AbstractShiroFilter shiroFilter = null;
49 try {
50 shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean
51 .getObject();
52 } catch (Exception e) {
53 throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!");
54 }
55 PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter
56 .getFilterChainResolver();
57 DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver
58 .getFilterChainManager();
59 // 清空老的权限控制
60 manager.getFilterChains().clear();
61 shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
62 shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitions());
63 // 重新构建生成
64 Map<String, String> chains = shiroFilterFactoryBean.getFilterChainDefinitionMap();
65 for (Map.Entry<String, String> entry : chains.entrySet()) {
66 String url = entry.getKey();
67 String chainDefinition = entry.getValue().trim().replace(" ", "");
68 manager.createChain(url, chainDefinition);
69 }
70 System.out.println("更新权限成功!!");
71 }
72 }
73}复制代码
74
3:修改shiroUtilsController
添加如下代码
1
2
3
4
5
6
7
8
9 1/**
2 * @Description: 重新加载shiro权限
3 * @throws Exception
4 */
5@PostMapping("/updatePermission")
6public void updatePermission() throws Exception {
7 shiroService.updatePermission();
8}复制代码
9
4:测试
输入localhost:8080/userInfo/selectById
修改权限
1
2
3
4
5 1UPDATE sys_permission_init
2SET permission_init = 'authc, roles[cw1]'
3WHERE
4id = 5复制代码
5
输入localhost:8080/shiroUtils/updatePermission 重新加载权限
注意:此刻我们并没有修改程序任何一个地方,也没有重启服务器
输入localhost:8080/userInfo/selectById
成功!
补充:生成加密之后的用户密码
文件在core→utils→test.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1public static void main(String []ages ){
2 //加密方式
3 String hashAlgorithmName = "md5";
4 //原密码
5 String credentials = "123456";
6 //加密次数
7 int hashIterations = 1024;
8 //加密盐值,大家可以用生成字符串的方法
9 String hash = "wxKYXuTPST5SG0jMQzVPsg==";
10 ByteSource credentialsSalt = ByteSource.Util.bytes(hash);
11 String password = new SimpleHash(hashAlgorithmName, credentials, credentialsSalt, hashIterations).toHex();
12 System.out.println(password);
13}复制代码
14
项目地址
码云地址: gitee.com/beany/mySpr…
GitHub地址: github.com/MyBeany/myS…
写文章不易,如对您有帮助,请帮忙点下star
结尾
添加shiro权限保护接口功能已完成,后续功能接下来陆续更新,有问题可以联系我mr_beany@163.com。另求各路大神指点,感谢大家。