先简单介绍一下Shiro,对于没有用过Shiro的朋友,也算是做个简介吧。
Shiro是Apache下的一个开源项目,我们称之为Apache Shiro。它是一个很易用与Java项目的的安全框架,提供了认证、授权、加密、会话管理,与 Spring Security 一样都是做一个权限的安全框架,但是与Spring Security 相比,在于 Shiro 使用了比较简单易懂易于使用的授权方式。
Apache Shiro 的三大核心组件
- Subject 当前用户操作
- SecurityManager 用于管理所有的Subject
- Realms 用于进行权限信息的验证,也是我们需要自己实现的。
我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。
既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
另外我们可以通过Shiro 提供的会话管理来获取Session中的信息。Shiro 也提供了缓存支持,使用 CacheManager 来管理。
官方网站:http://shiro.apache.org/
完整架构图:
下面我们通过代码实战来看下Spring Boot 中应用Shiro:
1、创建数据库表
------ -------------- --------- ------ ---------- ------ -------- ---------- ------- ---------
- 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
- 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
看截图,上面3张表是我测试别的用的,可以忽略。
下面是,数据库脚本和测试数据。
;;;;;;CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */;USE `test`;DROP TABLE IF EXISTS `t_permission`;CREATE TABLE `t_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT, `permissionname` varchar(32) DEFAULT NULL, `role_id` int(11) DEFAULT NULL, KEY `id` (`id`)) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;insert into `t_permission`(`id`,`permissionname`,`role_id`) values (1,'add',2),(2,'del',1),(3,'update',2),(4,'query',3),(5,'user:query',1),(6,'user:edit',2);DROP TABLE IF EXISTS `t_role`;CREATE TABLE `t_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `rolename` varchar(32) DEFAULT NULL, KEY `id` (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;insert into `t_role`(`id`,`rolename`) values (1,'admin'),(2,'manager'),(3,'normal');DROP TABLE IF EXISTS `t_user`;CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) DEFAULT NULL, `password` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;insert into `t_user`(`id`,`username`,`password`) values (1,'tom','123456'),(2,'jack','123456'),(3,'rose','123456');DROP TABLE IF EXISTS `t_user_role`;CREATE TABLE `t_user_role` ( `user_id` int(11) DEFAULT NULL, `role_id` int(11) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into `t_user_role`(`user_id`,`role_id`) values (1,1),(1,3),(2,2),(2,3),(3,3);;;;;
- 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
- 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
2、创建对应实体类
User.java
package org.springboot.sample.entity;import java.util.HashSet;import java.util.List;import java.util.Set;import javax.persistence.Entity;import javax.persistence.FetchType;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.JoinTable;import javax.persistence.ManyToMany;import javax.persistence.Table;import javax.persistence.Transient;import org.hibernate.validator.constraints.NotEmpty;/** * 用户 * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月13日 */@Entity@Table(name = "t_user")public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @NotEmpty(message = "用户名不能为空") private String username; @NotEmpty(message = "密码不能为空") private String password; @ManyToMany(fetch=FetchType.EAGER) @JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = { @JoinColumn(name = "role_id") }) private List<Role> roleList; public User() { super(); } public User(String username, String password) { super(); this.username = username; this.password = password; } @Transient public Set<String> getRolesName() { List<Role> roles = getRoleList(); Set<String> set = new HashSet<String>(); for (Role role : roles) { set.add(role.getRolename()); } return 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
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 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
Role.java
package org.springboot.sample.entity;import java.util.ArrayList;import java.util.List;import javax.persistence.Entity;import javax.persistence.FetchType;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.JoinTable;import javax.persistence.ManyToMany;import javax.persistence.OneToMany;import javax.persistence.Table;import javax.persistence.Transient;/** * 角色(管理员,普通用户等) * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月13日 */@Entity@Table(name = "t_role")public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String rolename; @OneToMany(mappedBy = "role", fetch=FetchType.EAGER) private List<Permission> permissionList; @ManyToMany @JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "role_id") }, inverseJoinColumns = { @JoinColumn(name = "user_id") }) private List<User> userList; @Transient public List<String> getPermissionsName() { List<String> list = new ArrayList<String>(); List<Permission> perlist = getPermissionList(); for (Permission per : perlist) { list.add(per.getPermissionname()); } return list; }}
- 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
- 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
Permission.java
package org.springboot.sample.entity;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.ManyToOne;import javax.persistence.Table;/** * 权限(增删改查等) * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月13日 */@Entity@Table(name = "t_permission")public class Permission { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String permissionname; @ManyToOne @JoinColumn(name = "role_id") private Role role; }
- 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
- 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
3、Shiro 配置,相当于SpringMVC 中的XML配置
ShiroConfiguration.java
package org.springboot.sample.config;import java.util.LinkedHashMap;import java.util.Map;import org.apache.shiro.cache.ehcache.EhCacheManager;import org.apache.shiro.spring.LifecycleBeanPostProcessor;import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springboot.sample.dao.IScoreDao;import org.springboot.sample.security.MyShiroRealm;import org.springboot.sample.service.StudentService;import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;import org.springframework.boot.context.embedded.FilterRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.filter.DelegatingFilterProxy;/** * Shiro 配置 * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月13日 */@Configurationpublic class ShiroConfiguration { private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class); @Bean public EhCacheManager getEhCacheManager() { EhCacheManager em = new EhCacheManager(); em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return em; } @Bean(name = "myShiroRealm") public MyShiroRealm myShiroRealm(EhCacheManager cacheManager) { MyShiroRealm realm = new MyShiroRealm(); realm.setCacheManager(cacheManager); return realm; } /** * 注册DelegatingFilterProxy(Shiro) * 集成Shiro有2种方法: * 1. 按这个方法自己组装一个FilterRegistrationBean(这种方法更为灵活,可以自己定义UrlPattern, * 在项目使用中你可能会因为一些很但疼的问题最后采用它, 想使用它你可能需要看官网或者已经很了解Shiro的处理原理了) * 2. 直接使用ShiroFilterFactoryBean(这种方法比较简单,其内部对ShiroFilter做了组装工作,无法自己定义UrlPattern, * 默认拦截 /*) * * @param dispatcherServlet * @return * @author SHANHY * @create 2016年1月13日 */ @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); daap.setProxyTargetClass(true); return daap; } @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(MyShiroRealm myShiroRealm) { DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager(); dwsm.setRealm(myShiroRealm); dwsm.setCacheManager(getEhCacheManager()); return dwsm; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); aasa.setSecurityManager(securityManager); return aasa; } /** * 加载shiroFilter权限控制规则(从数据库读取然后配置) * * @author SHANHY * @create 2016年1月14日 */ private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean, StudentService stuService, IScoreDao scoreDao){ Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/user", "authc"); logger.info("##################从数据库读取权限规则,加载到shiroFilter中##################"); filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/**", "anon"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); } /** * ShiroFilter<br/> * 注意这里参数中的 StudentService 和 IScoreDao 只是一个例子,因为我们在这里可以用这样的方式获取到相关访问数据库的对象, * 然后读取数据库相关配置,配置到 shiroFilterFactoryBean 的访问规则中。实际项目中,请使用自己的Service来处理业务逻辑。 * * @param myShiroRealm * @param stuService * @param scoreDao * @return * @author SHANHY * @create 2016年1月14日 */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, StudentService stuService, IScoreDao scoreDao) { ShiroFilterFactoryBean shiroFilterFactoryBean = new MShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setSuccessUrl("/user"); shiroFilterFactoryBean.setUnauthorizedUrl("/403"); loadShiroFilterChain(shiroFilterFactoryBean, stuService, scoreDao); return shiroFilterFactoryBean; }}
- 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
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 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
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
/** * 继承 ShiroFilterFactoryBean 处理拦截资源文件问题。 * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年3月8日 */public class MShiroFilterFactoryBean extends ShiroFilterFactoryBean { private Set<String> ignoreExt; public MShiroFilterFactoryBean() { super(); ignoreExt = new HashSet<>(); ignoreExt.add(".jpg"); ignoreExt.add(".png"); ignoreExt.add(".gif"); ignoreExt.add(".bmp"); ignoreExt.add(".js"); ignoreExt.add(".css"); } @Override protected AbstractShiroFilter createInstance() throws Exception { SecurityManager securityManager = getSecurityManager(); if (securityManager == null) { String msg = "SecurityManager property must be set."; throw new BeanInitializationException(msg); } if (!(securityManager instanceof WebSecurityManager)) { String msg = "The security manager does not implement the WebSecurityManager interface."; throw new BeanInitializationException(msg); } FilterChainManager manager = createFilterChainManager(); PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); chainResolver.setFilterChainManager(manager); return new MSpringShiroFilter((WebSecurityManager) securityManager, chainResolver); } private final class MSpringShiroFilter extends AbstractShiroFilter { protected MSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) { super(); if (webSecurityManager == null) { throw new IllegalArgumentException("WebSecurityManager property cannot be null."); } setSecurityManager(webSecurityManager); if (resolver != null) { setFilterChainResolver(resolver); } } @Override protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest)servletRequest; String str = request.getRequestURI().toLowerCase(); boolean flag = true; int idx = 0; if(( idx = str.indexOf(".")) > 0){ str = str.substring(idx); if(ignoreExt.contains(str.toLowerCase())) flag = false; } if(flag){ super.doFilterInternal(servletRequest, servletResponse, chain); }else{ chain.doFilter(servletRequest, servletResponse); } } }}
- 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
- 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
其中的 ehcache-shiro.xml 在 src/main/resources 下面,内容为:
<?xml version="1.0" encoding="UTF-8"?><ehcache updateCheck="false" name="shiroCache"> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /></ehcache>
4、继承 AuthorizingRealm 实现认证和授权2个方法
MyShiroRealm.java
package org.springboot.sample.security;import java.util.List;import org.apache.commons.lang3.builder.ReflectionToStringBuilder;import org.apache.commons.lang3.builder.ToStringStyle;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.SimpleAuthenticationInfo;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springboot.sample.dao.IUserDao;import org.springboot.sample.entity.Role;import org.springboot.sample.entity.User;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;/** * MyShiroRealm * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月13日 */public class MyShiroRealm extends AuthorizingRealm{ private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class); @Autowired private IUserDao userDao; /** * 权限认证,为当前登录的Subject授予角色和权限 * @see 经测试:本例中该方法的调用时机为需授权资源被访问时 * @see 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache * @see 经测试:如果连续访问同一个URL(比如刷新),该方法不会被重复调用,Shiro有一个时间间隔(也就是cache时间,在ehcache-shiro.xml中配置),超过这个时间间隔再刷新页面,该方法会被执行 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("##################执行Shiro权限认证##################"); String loginName = (String)super.getAvailablePrincipal(principalCollection); User user=userDao.findByName(loginName); if(user!=null){ SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(); info.setRoles(user.getRolesName()); List<Role> roleList=user.getRoleList(); for (Role role : roleList) { info.addStringPermissions(role.getPermissionsName()); } return info; } return null; } /** * 登录认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken; logger.info("验证当前Subject时获取到token为:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE)); User user=userDao.findByName(token.getUsername()); if(user!=null){ return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); } return null; }}
- 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
- 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
注意:其中 userDao.findByName 这个代码就不贴上了,也没啥可贴的,根据姓名查询一个对象而已。
5、编写测试的 Controller 和测试 jsp 页面
ShiroController.java
package org.springboot.sample.controllerimport java.util.Mapimport javax.validation.Validimport org.apache.shiro.SecurityUtilsimport org.apache.shiro.authc.AuthenticationExceptionimport org.apache.shiro.authc.ExcessiveAttemptsExceptionimport org.apache.shiro.authc.IncorrectCredentialsExceptionimport org.apache.shiro.authc.LockedAccountExceptionimport org.apache.shiro.authc.UnknownAccountExceptionimport org.apache.shiro.authc.UsernamePasswordTokenimport org.apache.shiro.subject.Subjectimport org.slf4j.Loggerimport org.slf4j.LoggerFactoryimport org.springboot.sample.dao.IUserDaoimport org.springboot.sample.entity.Userimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.stereotype.Controllerimport org.springframework.ui.Modelimport org.springframework.validation.BindingResultimport org.springframework.web.bind.annotation.PathVariableimport org.springframework.web.bind.annotation.RequestMappingimport org.springframework.web.bind.annotation.RequestMethodimport org.springframework.web.servlet.mvc.support.RedirectAttributes@Controllerpublic class ShiroController { private static final Logger logger = LoggerFactory.getLogger(ShiroController.class) @Autowired private IUserDao userDao @RequestMapping(value="/login",method=RequestMethod.GET) public String loginForm(Model model){ model.addAttribute("user", new User()) return "login" } @RequestMapping(value="/login",method=RequestMethod.POST) public String login(@Valid User user,BindingResult bindingResult,RedirectAttributes redirectAttributes){ if(bindingResult.hasErrors()){ return "login" } String username = user.getUsername() UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword()) //获取当前的Subject Subject currentUser = SecurityUtils.getSubject() try { //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查 //每个Realm都能在必要时对提交的AuthenticationTokens作出反应 //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法 logger.info("对用户[" + username + "]进行登录验证..验证开始") currentUser.login(token) logger.info("对用户[" + username + "]进行登录验证..验证通过") }catch(UnknownAccountException uae){ logger.info("对用户[" + username + "]进行登录验证..验证未通过,未知账户") redirectAttributes.addFlashAttribute("message", "未知账户") }catch(IncorrectCredentialsException ice){ logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证") redirectAttributes.addFlashAttribute("message", "密码不正确") }catch(LockedAccountException lae){ logger.info("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定") redirectAttributes.addFlashAttribute("message", "账户已锁定") }catch(ExcessiveAttemptsException eae){ logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多") redirectAttributes.addFlashAttribute("message", "用户名或密码错误次数过多") }catch(AuthenticationException ae){ //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景 logger.info("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下") ae.printStackTrace() redirectAttributes.addFlashAttribute("message", "用户名或密码不正确") } //验证是否登录成功 if(currentUser.isAuthenticated()){ logger.info("用户[" + username + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)") return "redirect:/user" }else{ token.clear() return "redirect:/login" } } @RequestMapping(value="/logout",method=RequestMethod.GET) public String logout(RedirectAttributes redirectAttributes ){ //使用权限管理工具进行用户的退出,跳出登录,给出提示信息 SecurityUtils.getSubject().logout() redirectAttributes.addFlashAttribute("message", "您已安全退出") return "redirect:/login" } @RequestMapping("/403") public String unauthorizedRole(){ logger.info("------没有权限-------") return "403" } @RequestMapping("/user") public String getUserList(Map<String, Object> model){ model.put("userList", userDao.getList()) return "user" } @RequestMapping("/user/edit/{userid}") public String getUserList(@PathVariable int userid){ logger.info("------进入用户信息修改-------") return "user_edit" }}
- 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
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 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
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title>Login</title></head><body> <h1>登录页面----${message }</h1> <img alt="" src="${pageContext.request.contextPath }/pic.jpg"> <form:form action="${pageContext.request.contextPath }/login" commandName="user" method="post"> 用户名:<form:input path="username" /> <form:errors path="username" cssClass="error" /> <br /> 密码:<form:password path="password" /> <form:errors path="password" cssClass="error" /> <br /> <form:button name="button">提交</form:button> </form:form></body></html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
user.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html> <head> <title>用户列表</title> </head> <body> <h1>${message }</h1> <h1>用户列表--<a href="${pageContext.request.contextPath }/logout">退出登录</a> </h1> <h2>权限列表</h2> <shiro:authenticated>用户已经登录显示此内容<br/></shiro:authenticated><br/> <shiro:hasRole name="manager">manager角色登录显示此内容<br/></shiro:hasRole> <shiro:hasRole name="admin">admin角色登录显示此内容<br/></shiro:hasRole> <shiro:hasRole name="normal">normal角色登录显示此内容<br/></shiro:hasRole><br/> <shiro:hasAnyRoles name="manager,admin">manager or admin 角色用户登录显示此内容<br/></shiro:hasAnyRoles><br/> <shiro:principal/>-显示当前登录用户名<br/><br/> <shiro:hasPermission name="add">add权限用户显示此内容<br/></shiro:hasPermission> <shiro:hasPermission name="user:query">user:query权限用户显示此内容<br/></shiro:hasPermission> <shiro:lacksPermission name="user:query">不具有user:query权限的用户显示此内容 <br/></shiro:lacksPermission> <br/>所有用户列表:<br/> <ul> <c:forEach items="${userList }" var="user"> <li>用户名:${user.username }----密码:${user.password }----<a href="${pageContext.request.contextPath }/user/edit/${user.id}">修改用户(测试根据不同用户可访问权限不同,本例tom无权限,jack有权限)</a></li> </c:forEach> </ul> <img alt="" src="${pageContext.request.contextPath }/pic.jpg"> <script type="text/javascript" src="${pageContext.request.contextPath }/webjarslocator/jquery/jquery.js"></script> </body></html>
- 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
- 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
user_edit.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html> <head> <title>用户信息 - 修改</title> </head> <body> <h2>修改用户信息页面</h2><br/> <a href="${pageContext.request.contextPath }/user">返回用户列表</a> </body></html>
403.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html> <head> <title>权限错误</title> </head> <body> <h1>对不起,您没有权限请求此连接!</h1> <img alt="" src="${pageContext.request.contextPath }/pic.jpg"> </body></html>
其中的pic.jpg 是测试代码遗留的,没有任何用处。关于 Controller 和 JSP 页面本文不做介绍,关于Spring Boot 使用Controller 和 JSP ,前面已经有文章介绍。
启动服务后访问 http://localhost:8080/myspringboot/user 会自动跳到 login 页面。
登录成功后,会打开 user 页面(关于默认登录页、成功成功URL、没有权限URL,在 ShiroConfiguration 中已经配置)。
在 user 页面上,不同用户会根据权限不同显示不同的内容,下面的修改操作也已经有文字说明,更换账号测试便知。
然后我们在实际项目中:不但要在页面上控制不同权限隐藏或将某些操作设置为不可用状态,还要在实际上控制那个操作背后的请求是真的不可以使用的。(例如:页面上的修改按钮已经灰化了,而我知道了修改按钮正常情况下点击会触发的请求,此时我直接模拟这个修改请求,应当是没有权限的才对,这样才算是真正的控制了权限。)
附:
Filter Chain定义说明
1、一个URL可以配置多个Filter,使用逗号分隔
2、当设置多个过滤器时,全部验证通过,才视为通过
3、部分过滤器可指定参数,如perms,roles
Shiro内置的FilterChain
Filter Name | Class | anonorg.apache.shiro.web.filter.authc.AnonymousFilterauthcorg.apache.shiro.web.filter.authc.FormAuthenticationFilterauthcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilterpermsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilterportorg.apache.shiro.web.filter.authz.PortFilterrestorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilterrolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFiltersslorg.apache.shiro.web.filter.authz.SslFilteruserorg.apache.shiro.web.filter.authc.UserFilter 0 0