一篇文章搞定Shiro权限管理框架

前言:前几天学习了SpringSecurity安全框架,这几天又接着学习shiro框架,这两者框架都是同一类产品,解决同一类问题,但是在官方推荐使用Shiro框架,因为它简单易学,所以这里有时间学习了以下。

Shiro的作用
用于验证登陆用户的身份
用户访问权限控制和登陆的认证,1.用于用户登陆的验证,2.用户用户登录后的授权,也就是那些用户拥有访问那些接口的权限
可以响应认证、访问控制,或者 Session 生命周期中发生的事件
可将一个或以上用户安全数据源数据组合成一个复合的用户“view”(视图)
支持单点登录(SSO)功能
支持提供“Remember Me”服务,当用户第二次登陆时只要session还可用就不需要再次登陆
Shiro的优点
易于上手
灵活——Apache Shiro可以在任何应用程序环境中工作。虽然在网络工作、EJB和IoC环境中可能并不需要它。但Shiro的授权也没有任何规范,甚至没有许多依赖关系。
Web支持——Apache Shiro拥有令人兴奋的web应用程序支持,允许您基于应用程序的url创建灵活的安全策略和网络协议(例如REST),同时还提供一组JSP库控制页面输出。
低耦合——Shiro干净的API和设计模式使它容易与许多其他框架和应用程序集成。你会看到Shiro无缝地集成Spring这样的框架,以及Grails, Wicket, Tapestry, Mule, Apache Camel, Vaadin…等。
被广泛支持——Apache Shiro是Apache软件基金会的一部分。项目开发和用户组都有友好的网民愿意帮助。这样的商业公司如果需要Katasoft还提供专业的支持和服务。
Apache Shiro架构
下图为描述Shiro的架构图:
在这里插入图片描述
Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)是Shiro框架的四大基石

Authentication(认证):用于用户登陆时的认证
Authorization(授权):访问控制。指定哪些用户拥有访问哪些接口的权限
Session Management(会话管理):特定于用户的会话管理。
Cryptography(加密):对用户的登陆的信息进行加密
其它特点:

Web支持:Shiro的Web支持API有助于保护Web应用程序。 缓存:缓存是Apache Shiro
API中的第一级,以确保安全操作保持快速和高效。
并发性:Apache Shiro支持具有并发功能的多线程应用程序。
测试:存在测试支持,可帮助您编写单元测试和集成测试,并确保代码按预期得到保障。
“运行方式”:允许用户承担另一个用户的身份(如果允许)的功能,有时在管理方案中很有用。
“记住我”:记住用户在会话中的身份,所以用户只需要强制登录即可。
注意: Shiro不会去维护用户、维护权限,这些需要我们自己去设计/提供,然后通过相应的接口注入给Shiro

在概念层,Shiro 中的重要概念:Subject,SecurityManager和 Realm。
在这里插入图片描述

Subject:当前用户,Subject可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
SecurityManager:管理所有Subject,SecurityManager 是 Shiro架构的核心,配合内部安全组件共同组成安全伞。
Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm来实现认证(authentication)和/或授权(authorization)。
Shiro 认证流程
在这里插入图片描述

Shiro 实战
(1)创建SpringBooot项目,pom.xml依赖如下:


4.0.0

org.springframework.boot
spring-boot-starter-parent
1.5.16.RELEASE


com.ldc.org
shirodemo
0.0.1-SNAPSHOT
war
shirodemo
Demo project for shiro


	1.8



	
		org.mybatis.spring.boot
		mybatis-spring-boot-starter
		1.3.1
	
	
		org.springframework.boot
		spring-boot-starter-web
	
	
		mysql
		mysql-connector-java
		runtime
	
	
		org.springframework.boot
		spring-boot-starter-tomcat
		provided
	
	
		org.apache.shiro
		shiro-core
		1.3.2
	
	
		org.apache.shiro
		shiro-spring
		1.3.2
	
	
		com.alibaba
		druid
		1.0.20
	
	
		org.apache.commons
		commons-lang3
		3.4
	
	
		org.springframework
		spring-context-support
		4.2.3.RELEASE
	
	
		org.apache.tomcat.embed
		tomcat-embed-jasper
	
	
		javax.servlet
		javax.servlet-api
	
	
		javax.servlet
		jstl
	



	
		
			org.springframework.boot
			spring-boot-maven-plugin
		
	

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
SpringBoot整合Mybaties进行数据库连接
SpringBoot整合Shiro框架
SpringBoot整合jsp并使用jstl表达式
SpringBoot整合阿里巴巴的Druid
(2)application.properties的配置如下:

database##

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=user

mybaties

mybatis.mapper-locations=mappers/*.xml
mybatis.type-aliases-package=com.ldc.org.shirodemo.pojo

jsp

spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这些都是一些常用的配置,这里就不再赘述,不懂得自行百度
(3)创建与数据库对应的实体类,主要有User、Role、Permission这三个实体类。代码如下:

package com.ldc.org.shirodemo.pojo;
public class Permission {

private Integer pid;

private String name;

private String url;

//get和set方法
}

package com.ldc.org.shirodemo.pojo;
import java.util.HashSet;
import java.util.Set;
public class Role {

private Integer rid;

private String rname;

private Set permissions=new HashSet<>();

private Set users=new HashSet<>();
//get和set方法

}

package com.ldc.org.shirodemo.pojo;
import java.util.HashSet;
import java.util.Set;
public class User {

private Integer uid;

private String username;

private String password;

private Set roles=new HashSet<>();
 //get和set方法

}
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
在这里插入图片描述
(4)创建mapper接口

package com.ldc.org.shirodemo.mapper;

import com.ldc.org.shirodemo.pojo.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {

User findByUsername(@Param("username") String username);

}
1
2
3
4
5
6
7
8
9
在这里插入图片描述
并在主方法的加上这些注解,指定mapper的位置,和开启组件扫描
在这里插入图片描述
(5)创建mapper.xml文件,代码如下:


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 在这里插入图片描述 (6)创建数据库test,sql语句如下,以及初始化一些测试数据:

– 权限表–
CREATE TABLE permission (
pid int(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL DEFAULT ‘’,
url VARCHAR(255) DEFAULT ‘’,
PRIMARY KEY (pid)
) ENGINE =InnoDB DEFAULT CHARSET = utf8;

INSERT INTO permission VALUES (‘1’,‘add’,’’);
INSERT INTO permission VALUES (‘2’,‘delete’,’’);
INSERT INTO permission VALUES (‘3’,‘edit’,’’);
INSERT INTO permission VALUES (‘4’,‘query’,’’);

– 用户表–
CREATE TABLE user(
uid int(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(255) NOT NULL DEFAULT ‘’,
password VARCHAR(255) NOT NULL DEFAULT ‘’,
PRIMARY KEY (uid)
) ENGINE =InnoDB DEFAULT CHARSET=utf8;

INSERT INTO user VALUES (‘1’,‘admin’,‘123’);
INSERT INTO user VALUES (‘2’,‘demo’,‘123’);

– 角色表–
CREATE TABLE role(
rid int(11) NOT NULL AUTO_INCREMENT,
rname VARCHAR(255) NOT NULL DEFAULT ‘’,
PRIMARY KEY (rid)
) ENGINE =InnoDB DEFAULT CHARSET=utf8;

INSERT INTO role VALUES (‘1’,‘admin’);
INSERT INTO role VALUES (‘2’,‘customer’);

– 权限角色关系表–
CREATE TABLE permission_role(
rid int(11) NOT NULL ,
pid int(11) NOT NULL ,
KEY idx_rid(rid),
KEY idx_pid(pid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO permission_role VALUES (‘1’,‘1’);
INSERT INTO permission_role VALUES (‘1’,‘2’);
INSERT INTO permission_role VALUES (‘1’,‘3’);
INSERT INTO permission_role VALUES (‘1’,‘4’);
INSERT INTO permission_role VALUES (‘2’,‘1’);
INSERT INTO permission_role VALUES (‘2’,‘4’);

– 用户角色关系表–
CREATE TABLE user_role(
uid int(11) NOT NULL ,
rid int(11) NOT NULL ,
KEY idx_uid(uid),
KEY idx_rid(rid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO user_role VALUES (1,1);
INSERT INTO user_role VALUES (2,2);

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
(7)创建AuthRealm,实现Shiro框架的AuthorizingRealm,代码如下:

package com.ldc.org.shirodemo;

import com.ldc.org.shirodemo.pojo.Permission;
import com.ldc.org.shirodemo.pojo.Role;
import com.ldc.org.shirodemo.pojo.User;
import com.ldc.org.shirodemo.service.UserService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class AuthRealm extends AuthorizingRealm {

@Autowired
private UserService userService;

// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    User user= (User) principals.fromRealm(this.getClass().getName()).iterator().next();
    List permissionList =new ArrayList<>();
    List roleNameList =new ArrayList<>();
    Set roleSet=user.getRoles();
    if (CollectionUtils.isNotEmpty(roleSet)){
        for(Role role:roleSet){
            roleNameList.add(role.getRname());
            Set permissionSet = role.getPermissions();
            if (CollectionUtils.isNotEmpty(permissionSet)){
                for(Permission permission:permissionSet){
                    permissionList.add(permission.getName());
                }
            }
        }
    }
    SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
    info.addStringPermissions(permissionList);
    info.addRoles(roleNameList);
    return info;
}

//认证登陆
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    UsernamePasswordToken usernamePasswordToken=(UsernamePasswordToken) token;
    String username = usernamePasswordToken.getUsername();
    User user = userService.findByUsername(username);
    return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());

}

}

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
(8)创建Shiro的配置类,如下:

package com.ldc.org.shirodemo;

import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.servlet.ShiroFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

@Configuration
public class ShiroConfiguration {

@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager){
    ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
    bean.setSecurityManager(manager);

    bean.setLoginUrl("/login");
    bean.setSuccessUrl("/index");
    bean.setUnauthorizedUrl("/unauthorized");

    LinkedHashMap filterChainDefinitionMap=new LinkedHashMap<>();
    //authc标识只有登录后才有权限访问,anon标识没有登陆也有权限访问
    filterChainDefinitionMap.put("/index","authc");
    filterChainDefinitionMap.put("/login","anon");
    filterChainDefinitionMap.put("/loginUser","anon");
    //admin接口只允许admin角色访问
    filterChainDefinitionMap.put("/admin","roles[admin]");
    filterChainDefinitionMap.put("/edit","perms[edit]");
    //放开druid的所有请求,可以访问druid监控

// filterChainDefinitionMap.put("/druid/",“anon”);
filterChainDefinitionMap.put("/
",“user”);
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}

@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm){
    DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
    manager.setRealm(authRealm);
    return manager;
}

@Bean("authRealm")
public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher credentialMatcher){
    AuthRealm authRealm=new AuthRealm();
    //使用缓存
    authRealm.setCacheManager(new MemoryConstrainedCacheManager());
    authRealm.setCredentialsMatcher(credentialMatcher);
    return authRealm;
}

@Bean("credentialMatcher")
public CredentialMatcher credentialMatcher(){
    return new CredentialMatcher();
}

/**
 * 以下的两个方法是设置shiro与spring之间的关联
 * @param securityManager
 * @return
 */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){
    AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
    advisor.setSecurityManager(securityManager);
    return advisor;
}

@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
    DefaultAdvisorAutoProxyCreator creator=new DefaultAdvisorAutoProxyCreator();
    creator.setProxyTargetClass(true);
    return creator;
}

}

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
(9)创建CredentialMatcher,作为密码比较的规则验证:

package com.ldc.org.shirodemo;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;

/**

  • 密码校验规则
    */
    public class CredentialMatcher extends SimpleCredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    UsernamePasswordToken usernamePasswordToken=(UsernamePasswordToken) token;
    String password=new String(usernamePasswordToken.getPassword());
    String dbPassword = (String) info.getCredentials();
    return this.equals(password,dbPassword);
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(10)Service层以及Service的实现:

package com.ldc.org.shirodemo.service;

import com.ldc.org.shirodemo.pojo.User;

public interface UserService {

User findByUsername( String username);

}

1
2
3
4
5
6
7
8
9
10
package com.ldc.org.shirodemo.service;

import com.ldc.org.shirodemo.mapper.UserMapper;
import com.ldc.org.shirodemo.pojo.User;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {

@Resource
private UserMapper userMapper;

@Override
public User findByUsername(String username) {
    return userMapper.findByUsername(username);
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(10)Controller层:

package com.ldc.org.shirodemo.controller;

import com.ldc.org.shirodemo.pojo.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;

@Controller
public class UserController {

@RequestMapping("/login")
public String login(){
    return "login";
}

/**
 * 两个case:
 * 第一个是只有登录后才能访问相关的接口,没有登陆是不允许访问相关的接口,例如admin接口
 * 第二个是某些接口只能被某些角色来访问
 * @return
 */
@RequestMapping("/admin")
@ResponseBody
public String admin(){
    return "admin success";
}

@RequestMapping("/loginOut")
public String loginOut(){
    Subject subject = SecurityUtils.getSubject();
    if (subject!=null){
        subject.logout();
    }
    return "login";
}

@RequestMapping("/index")
public String index(){
    return "index";
}

@RequestMapping("/edit")
@ResponseBody
public String edit(){
    return "edit success";
}

@RequestMapping("/unauthorized")
public String unauthorized(){
    return "unauthorized";
}

@RequestMapping("/loginUser")
public String loginUser(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        HttpSession session){
    UsernamePasswordToken token=new UsernamePasswordToken(username,password);
    Subject subject = SecurityUtils.getSubject();
    try {
        subject.login(token);
        User user= (User) subject.getPrincipal();
        session.setAttribute("user",user);
        return "index";
    }catch (Exception e){
        e.printStackTrace();
        return "login";
    }
}

}

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
(11)jsp页面:

<%@ page contentType=“text/html;charset=UTF-8” language=“java” %>

Home

欢迎登陆, ${user.username}

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page contentType=“text/html;charset=UTF-8” language=“java” %>

Login

欢迎登陆



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page contentType=“text/html;charset=UTF-8” language=“java” %>

Unauthorized

Unauthorized!

1
2
3
4
5
6
7
8
9
10
11
最后的项目的结构,如图所示:
在这里插入图片描述
(12)测试–其实前面粘贴了一大堆的代码都是为后面的测试准备,这里讲解的Shiro的技术点,主要有三点:

第一个是只有登录后才能访问相关的接口,没有登陆是不允许访问相关的接口,例如admin接口
第二个是某些接口只能被某些角色来访问
第三点某些接口只能被特定权限的才能访问
以下是验证这三点的过程:
例如,index接口,只有登陆才能访问,启动项目,直接输入localhost:8080/index是不能访问首页的,会被重定向会登陆页面:

你可能感兴趣的:(一篇文章搞定Shiro权限管理框架)