本来想写一篇spring boot整合Shiro实现权限验证的文章,发现这篇写的非常不错,就直接借鉴了!

(1). Shiro简单介绍

Shiro是Apache下的一个开源项目,我们称之为Apache Shiro。它是一个很易用与Java​项目的的安全框架,提供了认证、授权、加密、会话管理,与spring Security 一样都是做一个权限的安全框架,但是与Spring Security 相比,在于 Shiro 使用了比较简单易懂易于使用的授权方式。

Apache Shiro 的三大核心组件

Spring Boot整合jpa,Shiro进行权限管理_html

- 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整合jpa,Shiro进行权限管理_java_02

Shiro是很强大的一个安全框架,这里只是抛装引玉下,还有很多的需要大家自己去学习Shiro。

(2). 集成Shiro核心分析

      集成Shiro的话,我们需要知道Shiro框架大概的一些管理对象。

第一:ShiroFilterFactory,Shiro过滤器工厂类,具体的实现类是:ShiroFilterFactoryBean,此实现类是依赖于SecurityManager安全管理器。

第二:SecurityManager,Shiro的安全管理,主要是身份认证的管理,缓存管理,cookie管理,所以在实际开发中我们主要是和SecurityManager进行打交道的,ShiroFilterFactory主要配置好了Filter就可以了。当然SecurityManager并进行身份认证缓存的实现,我们需要进行对应的编码然后进行注入到安全管理器中。

第三:Realm,用于身份信息权限信息的验证。

第四:其它的就是缓存管理,记住登录之类的,这些大部分都是需要自己进行简单的实现,然后注入到SecurityManager让Shiro的安全管理器进行管理就好了。

(3). 无Shiro的Spring Boot

      我们先编写一个无Shiro的简单的框架,在这个框架中我们可以访问到index,login,userInfo,userInfoAdd。

      这个步骤对于有Spring Boot基础的就应该很简单了,在这里简单的介绍下:

(a) 新建一个maven Java project,取名为spring-boot-shiro1

(b) 在pom.xml中引入基本依赖,在这里还没有引入shiro等的依赖:

​[html] 

view plain

 copy

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>

<artifactId>spring-boot-shiro1</artifactId>

<version>0.0.1-SNAPSHOT</version>

<packaging>jar</packaging>

<properties>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

</properties>

<!-- Inherit defaults from Spring Boot -->

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>1.4.0.RELEASE</version>

</parent>

<dependencies>

<!-- spring boot web支持:mvc,aop... -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<!-- thmleaf模板依赖. -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-thymeleaf</artifactId>

</dependency>

<!-- 热部署 -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-devtools</artifactId>

<optional>true</optional>

</dependency>

</dependencies>

</project>

(c) 编写网页文件:

index.html,login.html,userInfo.html,userInfoAdd.html

这个文件存在在src/main/resouces/templates, 这几个文件中都是简单的代码,只有登录界面中有账号和密码:

index.html

​[html] 

view plain

 copy

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8" />
  5. <title>Insert title here</title>
  6. </head>
  7. <body>
  8. <h3>index</h3>
  9. </body>
  10. </html>

login.html

​[html] 

view plain

 copy

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8" />
  5. <title>Insert title here</title>
  6. </head>
  7. <body>
  8. 错误信息:<h4 th:text="${msg}"></h4>
  9. <form action="" method="post">
  10. <p>账号:<input type="text" name="username" value="admin"/></p>
  11. <p>密码:<input type="text" name="password" value="123456"/></p>
  12. <p><input type="submit" value="登录"/></p>
  13. </form>
  14. </body>
  15. </html>

userInfo.html,

​[html] 

view plain

 copy

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8" />
  5. <title>Insert title here</title>
  6. </head>
  7. <body>
  8. <h3>用户查询界面</h3>
  9. </body>
  10. </html>

userInfoAdd.html

<h3>用户添加界面</h3>

(d)编写启动类

​[html] 

view plain

 copy

  1. package com.example;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class Application {
  6. public static void main(String[] args) {
  7. SpringApplication.run(Application.class, args);
  8. }
  9. }

(e)编写HomeController类

新建HomeController类

​[html] 

view plain

 copy

  1. package com.example.controller;
  2. import java.util.Map;
  3. import javax.servlet.http.HttpServletRequest;
  4. import org.apache.shiro.authc.IncorrectCredentialsException;
  5. import org.apache.shiro.authc.UnknownAccountException;
  6. import org.springframework.stereotype.Controller;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RequestMethod;
  9. @Controller
  10. public class HomeController {
  11. @RequestMapping({ "/", "index" })
  12. public String index() {
  13. return "/index";
  14. }
  15. @RequestMapping(value = "/login", method = RequestMethod.GET)
  16. public String login() {
  17. return "/login";
  18. }
  19. }

集成shiro大概分这么一个步骤:

(a) pom.xml中添加Shiro依赖;

(b) 注入Shiro Factory和SecurityManager。

(c) 身份认证

(d) 权限控制

(a) pom.xml中添加Shiro依赖;

要使用Shiro进行权限控制,那么很明显的就需要添加对Shiro的依赖包,在pom.xml中加入如下配置:

​[html] 

view plain

 copy

  1. <!-- shiro spring. -->
  2. <dependency>
  3. <groupId>org.apache.shiro</groupId>
  4. <artifactId>shiro-spring</artifactId>
  5. <version>1.2.3</version>
  6. </dependency>

(b) 注入Shiro Factory和SecurityManager。

      在Spring中注入类都是使用配置文件的方式,在Spring Boot中是使用注解的方式,那么应该如何进行实现呢?

      Shiro几个核心的类,第一就是ShiroFilterFactory,第二就是SecurityManager,那么最简单的配置就是注入这两个类就ok了,那么如何注入呢?看如下代码:

      新建ShiroConfiguration.java

​[html] 

view plain

 copy

  1. package com.example.config.shiro;
  2. import java.util.LinkedHashMap;
  3. import java.util.Map;
  4. import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
  5. import org.apache.shiro.mgt.SecurityManager;
  6. import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
  7. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  8. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  9. import org.springframework.context.annotation.Bean;
  10. import org.springframework.context.annotation.Configuration;
  11. @Configuration
  12. public class ShiroConfiguration {
  13. @Bean
  14. public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
  15. System.out.println("ShiroConfiguration.shiroFilter()");
  16. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  17. // 必须设置SecuritManager
  18. shiroFilterFactoryBean.setSecurityManager(securityManager);
  19. // 拦截器
  20. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
  21. // 配置退出过滤器,其中的具体代码Shiro已经替我们实现了
  22. filterChainDefinitionMap.put("/logout", "logout");
  23. // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
  24. // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
  25. filterChainDefinitionMap.put("/**", "authc");
  26. // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
  27. shiroFilterFactoryBean.setLoginUrl("/login");
  28. // 登录成功后要跳转的链接
  29. shiroFilterFactoryBean.setSuccessUrl("/index");
  30. // 未授权界面;
  31. shiroFilterFactoryBean.setUnauthorizedUrl("/403");
  32. shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
  33. return shiroFilterFactoryBean;
  34. }
  35. @Bean
  36. public SecurityManager securityManager() {
  37. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  38. return securityManager;
  39. }
  40. }

   这里说下:ShiroFilterFactory中已经由Shiro官方实现的过滤器:

Shiro内置的FilterChain

Filter Name

Class

anon

org.apache.shiro.web.filter.authc.AnonymousFilter

authc

org.apache.shiro.web.filter.authc.FormAuthenticationFilter

authcBasic

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

perms

org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

port

org.apache.shiro.web.filter.authz.PortFilter

rest

org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

roles

org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

ssl

org.apache.shiro.web.filter.authz.SslFilter

user


org.apache.shiro.web.filter.authc.UserFilter



anon:所有url都都可以匿名访问;

authc: 需要认证才能进行访问;

user:配置记住我或认证通过可以访问;

这几个是我们会用到的,在这里说明下,其它的请自行查询文档进行学习。

这时候我们运行程序,访问/index页面我们会发现自动跳转到了login页面,当然这个时候输入账号和密码是无法进行访问的。下面这才是重点:任何身份认证,如何权限控制。

(c) 身份认证

      在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.

认证实现

Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。

该方法主要执行以下操作:

1、检查提交的进行认证的令牌信息

2、根据令牌信息从数据源(通常为数据库)中获取用户信息

3、对用户信息进行匹配验证。

4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。

5、验证失败则抛出AuthenticationException异常信息。

而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo (),重写获取用户信息的方法。

既然需要进行身份权限控制,那么少不了创建用户实体类,权限实体类。

      在权限管理系统中,有这么几个角色很重要,这个要是不清楚的话,那么就很难理解,我们为什么这么编码了。第一是用户表:在用户表中保存了用户的基本信息,账号、密码、姓名,性别等;第二是:权限表(资源+控制权限):这个表中主要是保存了用户的URL地址,权限信息;第三就是角色表:在这个表重要保存了系统存在的角色;第四就是关联表:用户-角色管理表(用户在系统中都有什么角色,比如admin,vip等),角色-权限关联表(每个角色都有什么权限可以进行操作)。依据这个理论,我们进行来进行编码,很明显的我们第一步就是要进行实体类的创建。在这里我们使用Mysql和JPA进行操作数据库。

那么我们先在pom.xml中引入mysql和JPA的依赖:

UserInfo.java、SysRole.java、SysPermission.java至于之前的关联表我们使用JPA进行自动生成。

​[html] 

view plain

 copy

  1. <!-- Spirng data JPA依赖; -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-data-jpa</artifactId>
  5. </dependency>
  6. <!-- mysql驱动; -->
  7. <dependency>
  8. <groupId>mysql</groupId>
  9. <artifactId>mysql-connector-java</artifactId>
  10. </dependency>

配置src/main/resouces/application.properties配置数据库和jpa(application.properties新建一个即可):

​[html] 

view plain

 copy

  1. ########################################################
  2. ###datasource
  3. ########################################################
  4. spring.datasource.url = jdbc:mysql://localhost:3306/test
  5. spring.datasource.username = root
  6. spring.datasource.password = root
  7. spring.datasource.driverClassName = com.mysql.jdbc.Driver
  8. spring.datasource.max-active=20
  9. spring.datasource.max-idle=8
  10. spring.datasource.min-idle=8
  11. spring.datasource.initial-size=10
  12. ########################################################
  13. ### Java Persistence Api
  14. ########################################################
  15. # Specify the DBMS
  16. spring.jpa.database = MYSQL
  17. # Show or not log for each sql query
  18. spring.jpa.show-sql = true
  19. # Hibernate ddl auto (create, create-drop, update)
  20. spring.jpa.hibernate.ddl-auto = update
  21. # Naming strategy
  22. #[org.hibernate.cfg.ImprovedNamingStrategy | org.hibernate.cfg.DefaultNamingStrategy]
  23. spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy
  24. # stripped before adding them to the entity manager)
  25. spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

准备工作准备好之后,那么就可以编写实体类了:

UserInfo.java

​[html] 

view plain

 copy

  1. package com.example.domain;
  2. import java.io.Serializable;
  3. import java.util.List;
  4. import javax.persistence.Column;
  5. import javax.persistence.Entity;
  6. import javax.persistence.FetchType;
  7. import javax.persistence.GeneratedValue;
  8. import javax.persistence.Id;
  9. import javax.persistence.JoinColumn;
  10. import javax.persistence.JoinTable;
  11. import javax.persistence.ManyToMany;
  12. /**
  13. * 用户信息.
  14. * @author Administrator
  15. *
  16. */
  17. @Entity
  18. public class UserInfo implements Serializable {
  19. /**
  20. *
  21. */
  22. private static final long serialVersionUID = 1L;
  23. @Id
  24. @GeneratedValue
  25. private long uid;// 用户id
  26. @Column(unique = true)
  27. private String username;// 帐号
  28. private String name;// 名称(昵称或者真实姓名,不同系统不同定义)
  29. private String password; // 密码;
  30. private String salt;// 加密密码的盐
  31. private byte state;// 用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 ,
  32. // 1:正常状态,2:用户被锁定.
  33. @ManyToMany(fetch = FetchType.EAGER) // 立即从数据库中进行加载数据
  34. @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns = {
  35. @JoinColumn(name = "roleId") })
  36. private List<SysRole> roleList;// 一个用户具有多个角色
  37. public long getUid() {
  38. return uid;
  39. }
  40. public void setUid(long uid) {
  41. this.uid = uid;
  42. }
  43. public String getUsername() {
  44. return username;
  45. }
  46. public void setUsername(String username) {
  47. this.username = username;
  48. }
  49. public String getName() {
  50. return name;
  51. }
  52. public void setName(String name) {
  53. this.name = name;
  54. }
  55. public String getPassword() {
  56. return password;
  57. }
  58. public void setPassword(String password) {
  59. this.password = password;
  60. }
  61. public String getSalt() {
  62. return salt;
  63. }
  64. public void setSalt(String salt) {
  65. this.salt = salt;
  66. }
  67. public byte getState() {
  68. return state;
  69. }
  70. public void setState(byte state) {
  71. this.state = state;
  72. }
  73. public List<SysRole> getRoleList() {
  74. return roleList;
  75. }
  76. public void setRoleList(List<SysRole> roleList) {
  77. this.roleList = roleList;
  78. }
  79. /**
  80. * 密码盐.
  81. *
  82. * @return
  83. */
  84. public String getCredentialsSalt() {
  85. return this.username + this.salt;
  86. }
  87. @Override
  88. public String toString() {
  89. return "UserInfo [uid=" + uid + ", username=" + username + ", name=" + name + ", password=" + password
  90. + ", salt=" + salt + ", state=" + state + "]";
  91. }
  92. }

在这里salt主要是用来进行密码加密的,当然也可以使用明文进行编码测试,实际开发中还是建议密码进行加密。

getCredentialsSalt()

这个方法重新对盐重新进行了定义,用户名+salt,这样就更加不容易被破解了

SysRole.java

​[html] 

view plain

 copy

  1. package com.example.domain;
  2. import java.io.Serializable;
  3. import java.util.List;
  4. import javax.persistence.Entity;
  5. import javax.persistence.FetchType;
  6. import javax.persistence.GeneratedValue;
  7. import javax.persistence.Id;
  8. import javax.persistence.JoinColumn;
  9. import javax.persistence.JoinTable;
  10. import javax.persistence.ManyToMany;
  11. /**
  12. * 系统角色实体类;
  13. *
  14. * @author Administrator
  15. *
  16. */
  17. @Entity
  18. public class SysRole implements Serializable {
  19. private static final long serialVersionUID = 1L;
  20. @Id
  21. @GeneratedValue
  22. private Long id; // 编号
  23. private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
  24. private String description; // 角色描述,UI界面显示使用
  25. private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户
  26. // 角色 -- 权限关系:多对多关系;
  27. @ManyToMany(fetch = FetchType.EAGER)
  28. @JoinTable(name = "SysRolePermission", joinColumns = { @JoinColumn(name = "roleId") }, inverseJoinColumns = {
  29. @JoinColumn(name = "permissionId") })
  30. private List<SysPermission> permissions;
  31. // 用户 - 角色关系定义;
  32. @ManyToMany
  33. @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "roleId") }, inverseJoinColumns = {
  34. @JoinColumn(name = "uid") })
  35. private List<UserInfo> userInfos;// 一个角色对应多个用户
  36. public List<UserInfo> getUserInfos() {
  37. return userInfos;
  38. }
  39. public void setUserInfos(List<UserInfo> userInfos) {
  40. this.userInfos = userInfos;
  41. }
  42. public Long getId() {
  43. return id;
  44. }
  45. public void setId(Long id) {
  46. this.id = id;
  47. }
  48. public String getRole() {
  49. return role;
  50. }
  51. public void setRole(String role) {
  52. this.role = role;
  53. }
  54. public String getDescription() {
  55. return description;
  56. }
  57. public void setDescription(String description) {
  58. this.description = description;
  59. }
  60. public Boolean getAvailable() {
  61. return available;
  62. }
  63. public void setAvailable(Boolean available) {
  64. this.available = available;
  65. }
  66. public List<SysPermission> getPermissions() {
  67. return permissions;
  68. }
  69. public void setPermissions(List<SysPermission> permissions) {
  70. this.permissions = permissions;
  71. }
  72. @Override
  73. public String toString() {
  74. return "SysRole [id=" + id + ", role=" + role + ", description=" + description + ", available=" + available
  75. + ", permissions=" + permissions + "]";
  76. }
  77. }

SysPermission.java

​[html] 

view plain

 copy

  1. package com.example.domain;
  2. import java.io.Serializable;
  3. import java.util.List;
  4. import javax.persistence.Column;
  5. import javax.persistence.Entity;
  6. import javax.persistence.FetchType;
  7. import javax.persistence.GeneratedValue;
  8. import javax.persistence.Id;
  9. import javax.persistence.JoinColumn;
  10. import javax.persistence.JoinTable;
  11. import javax.persistence.ManyToMany;
  12. /**
  13. * 权限实体类;
  14. *
  15. */
  16. @Entity
  17. public class SysPermission implements Serializable {
  18. private static final long serialVersionUID = 1L;
  19. @Id
  20. @GeneratedValue
  21. private long id;// 主键.
  22. private String name;// 名称.
  23. @Column(columnDefinition = "enum('menu','button')")
  24. private String resourceType;// 资源类型,[menu|button]
  25. private String url;// 资源路径.
  26. private String permission; // 权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
  27. private Long parentId; // 父编号
  28. private String parentIds; // 父编号列表
  29. private Boolean available = Boolean.FALSE;
  30. //  @ManyToMany(fetch = FetchType.LAZY)
  31. //  @JoinTable(name = "SysRolePermission", joinColumns = { @JoinColumn(name = "permissionId") }, inverseJoinColumns = {
  32. //          @JoinColumn(name = "roleId") })
  33. //  private List<SysRole> roles;
  34. public long getId() {
  35. return id;
  36. }
  37. public void setId(long id) {
  38. this.id = id;
  39. }
  40. public String getName() {
  41. return name;
  42. }
  43. public void setName(String name) {
  44. this.name = name;
  45. }
  46. public String getResourceType() {
  47. return resourceType;
  48. }
  49. public void setResourceType(String resourceType) {
  50. this.resourceType = resourceType;
  51. }
  52. public String getUrl() {
  53. return url;
  54. }
  55. public void setUrl(String url) {
  56. this.url = url;
  57. }
  58. public String getPermission() {
  59. return permission;
  60. }
  61. public void setPermission(String permission) {
  62. this.permission = permission;
  63. }
  64. public Long getParentId() {
  65. return parentId;
  66. }
  67. public void setParentId(Long parentId) {
  68. this.parentId = parentId;
  69. }
  70. public String getParentIds() {
  71. return parentIds;
  72. }
  73. public void setParentIds(String parentIds) {
  74. this.parentIds = parentIds;
  75. }
  76. public Boolean getAvailable() {
  77. return available;
  78. }
  79. public void setAvailable(Boolean available) {
  80. this.available = available;
  81. }
  82. //  public List<SysRole> getRoles() {
  83. //      return roles;
  84. //  }
  85. //
  86. //  public void setRoles(List<SysRole> roles) {
  87. //      this.roles = roles;
  88. //  }
  89. @Override
  90. public String toString() {
  91. return "SysPermission [id=" + id + ", name=" + name + ", resourceType=" + resourceType + ", url=" + url
  92. + ", permission=" + permission + ", parentId=" + parentId + ", parentIds=" + parentIds + ", available="
  93. + available + "]";
  94. }
  95. }

ok,到这里实体类就编码完毕了,这时候运行Application,就会自动建表

MySQL> show tables;

+---------------------+

| Tables_in_test      |

+---------------------+

| sys_permission      |

| sys_role            |

| sys_role_permission |

| sys_user_role       |

| user_info           |

+---------------------+

5 rows in set (0.08 sec)

mysql>

sql

​[html] 

view plain

 copy

  1. INSERT INTO `sys_permission`(name,parent_id,parent_ids,available,permission,resource_type,url)
  2. VALUES ('用户管理',0,'0/' ,1,'userInfo:view', 'menu', 'userInfo/userList');
  3. INSERT INTO `sys_permission`(name,parent_id,parent_ids,available,permission,resource_type,url)
  4. VALUES ('用户添加',1,'0/1',1,'userInfo:add', 'button', 'userInfo/userAdd');
  5. INSERT INTO `sys_permission`(name,parent_id,parent_ids,available,permission,resource_type,url)
  6. VALUES ('用户删除',1,'0/1',1,'userInfo:del', 'button', 'userInfo/userDel');
  7. INSERT INTO `sys_role`(available,description,role) VALUES (1,'管理员','admin');
  8. INSERT INTO `sys_role`(available,description,role) VALUES (1,'VIP会员','vip');
  9. INSERT INTO `sys_role_permission`(permission_id,role_id) VALUES ('1', '1');
  10. INSERT INTO `sys_role_permission`(permission_id,role_id) VALUES ('2', '1');
  11. INSERT INTO `user_info`(name,password,salt,state,username) VALUES ('管理员', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', '0', 'admin');
  12. INSERT INTO `sys_user_role`(uid,role_id) VALUES (1,1);
  13. INSERT INTO `sys_user_role`(uid,role_id) VALUES (1,2);

这时候数据都准备完毕了,那么接下来就应该编写Repository进行访问数据了

​[html] 

view plain

 copy

  1. package com.example.repository;
  2. import org.springframework.data.repository.CrudRepository;
  3. import com.example.domain.UserInfo;
  4. /**
  5. * UserInfo持久化类
  6. *
  7. * @author Administrator
  8. *
  9. */
  10. public interface UserInfoRepository extends CrudRepository<UserInfo, Long> {
  11. /** 通过username查找用户信息 **/
  12. public UserInfo findByUsername(String username);
  13. }

在这里你会发现我们只编写了UserInfo的数据库操作,那么我们怎么获取我们的权限信息了,通过userInfo.getRoleList()可以获取到对应的角色信息,然后在通过对应的角色可以获取到权限信息,当然这些都是JPA帮我们实现了,我们也可以进行直接获取到权限信息,只要写一个关联查询然后过滤掉重复的权限即可,这里不进行实现。

编写一个业务处理类UserInfoService>

​[html] 

view plain

 copy

  1. package com.example.service;
  2. import com.example.domain.UserInfo;
  3. public interface UserInfoService {
  4. public UserInfo findByUsername(String username);
  5. }

​[html] 

view plain

 copy

  1. package com.example.service.impl;
  2. import javax.annotation.Resource;
  3. import org.springframework.stereotype.Service;
  4. import org.springframework.transaction.annotation.Transactional;
  5. import com.example.domain.UserInfo;
  6. import com.example.repository.UserInfoRepository;
  7. import com.example.service.UserInfoService;
  8. @Service
  9. public class UserInfoServiceImpl implements UserInfoService{
  10. @Resource
  11. private UserInfoRepository userInfoRepository;
  12. @Transactional(readOnly=true)
  13. @Override
  14. public UserInfo findByUsername(String username) {
  15. System.out.println("UserInfoServiceImpl.findByUsername()");
  16. return userInfoRepository.findByUsername(username);
  17. }
  18. }

基本工作准备好之后,剩下的才是重点,shiro的认证最终是交给了Realm进行执行了,所以我们需要自己重新实现一个Realm,此Realm继承AuthorizingRealm。

​[html] 

view plain

 copy

  1. package com.example.config.shiro;
  2. import javax.annotation.Resource;
  3. import org.apache.shiro.authc.AuthenticationException;
  4. import org.apache.shiro.authc.AuthenticationInfo;
  5. import org.apache.shiro.authc.AuthenticationToken;
  6. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  7. import org.apache.shiro.authz.AuthorizationInfo;
  8. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  9. import org.apache.shiro.realm.AuthorizingRealm;
  10. import org.apache.shiro.subject.PrincipalCollection;
  11. import org.apache.shiro.util.ByteSource;
  12. import org.springframework.context.annotation.Bean;
  13. import com.example.domain.SysPermission;
  14. import com.example.domain.SysRole;
  15. import com.example.domain.UserInfo;
  16. import com.example.service.UserInfoService;
  17. /**
  18. * 身份校验核心类
  19. *
  20. * @author Administrator
  21. *
  22. */
  23. public class MyShiroRealm extends AuthorizingRealm {
  24. @Resource
  25. private UserInfoService userInfoService;
  26. /**
  27. * 认证信息(身份验证) Authentication 是用来验证用户身份
  28. */
  29. @Override
  30. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  31. System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
  32. // 获取用户的输入帐号
  33. String username = (String) token.getPrincipal();
  34. System.out.println(token.getCredentials());
  35. // 通过username从数据库中查找 User对象,如果找到,没找到.
  36. // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
  37. UserInfo userInfo = userInfoService.findByUsername(username);
  38. System.out.println("----->>userInfo=" + userInfo);
  39. if (userInfo == null) {
  40. return null;
  41. }
  42. SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 用户名
  43. userInfo.getPassword(), // 密码
  44. ByteSource.Util.bytes(userInfo.getCredentialsSalt()), // salt=username+salt
  45. getName() // realm name
  46. );
  47. return authenticationInfo;
  48. }
  49. @Override
  50. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  51. // TODO Auto-generated method stub
  52. System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
  53. SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
  54. UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();
  55. for(SysRole role:userInfo.getRoleList()){
  56. authorizationInfo.addRole(role.getRole());
  57. System.out.println(role.getPermissions());
  58. for(SysPermission p:role.getPermissions()){
  59. System.out.println(p);
  60. authorizationInfo.addStringPermission(p.getPermission());
  61. }
  62. }
  63. return authorizationInfo;
  64. }
  65. }

继承AuthorizingRealm主要需要实现两个方法:

doGetAuthenticationInfo();

doGetAuthorizationInfo();

其中doGetAuthenticationInfo主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。

​[html] 

view plain

 copy

  1. SimpleAuthenticationInfoauthenticationInfo =
  2. new SimpleAuthenticationInfo(
  3. userInfo, //用户名
  4. userInfo.getPassword(), //密码
  5. ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
  6. getName()  //realm name
  7. );

交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现

如果你是进行明文进行编码的话,那么使用使用如下方式:

​[html] 

view plain

 copy

  1. SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
  2. userInfo, //用户名
  3. userInfo.getPassword(), //密码
  4. getName()  //realm name
  5. );

至于doGetAuthorizationInfo()是权限控制,当访问到页面的时候,使用了相应的注解或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。

在这个方法中主要是使用类:SimpleAuthorizationInfo

进行角色的添加和权限的添加。

authorizationInfo.addRole(role.getRole());

authorizationInfo.addStringPermission(p.getPermission());

当然也可以添加集合:

authorizationInfo.setRoles(roles);

authorizationInfo.setStringPermissions(stringPermissions);

到这里我们还需要有一个步骤很重要就是将我们自定义的Realm注入到SecurityManager中。

在ShiroConfiguration.java中添加方法

​[html] 

view plain

 copy

  1. /**
  2. * 身份认证realm;
  3. *
  4. */
  5. @Bean
  6. public MyShiroRealm myShiroRealm(){
  7. MyShiroRealm myShiroRealm = new MyShiroRealm();
  8. return myShiroRealm;
  9. }

将myShiroRealm注入到securityManager中:

​[html] 

view plain

 copy

  1. @Bean
  2. public SecurityManager securityManager(){
  3. DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
  4. //设置realm.
  5. securityManager.setRealm(myShiroRealm());
  6. return securityManager;
  7. }

到这里的话身份认证权限控制基本是完成了,最后我们在编写一个登录的时候,登录的处理:

在HomeController中添加login post处理:

​[html] 

view plain

 copy

  1. package com.example.controller;
  2. import java.util.Map;
  3. import javax.servlet.http.HttpServletRequest;
  4. import org.apache.shiro.authc.IncorrectCredentialsException;
  5. import org.apache.shiro.authc.UnknownAccountException;
  6. import org.springframework.stereotype.Controller;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RequestMethod;
  9. @Controller
  10. public class HomeController {
  11. @RequestMapping({ "/", "index" })
  12. public String index() {
  13. return "/index";
  14. }
  15. @RequestMapping(value = "/login", method = RequestMethod.GET)
  16. public String login() {
  17. return "/login";
  18. }
  19. @RequestMapping(value = "/login", method = RequestMethod.POST)
  20. public String login(HttpServletRequest request, Map<String, Object> map) {
  21. System.out.println("HomeController.login");
  22. // 登录失败从request中获取shiro处理的异常信息
  23. // shiroLoginFailure:就是shiro异常类的全类名
  24. String exception = (String) request.getAttribute("shiroLoginFailure");
  25. String msg = "";
  26. if (exception != null) {
  27. if (UnknownAccountException.class.getName().equals(exception)) {
  28. System.out.println("UnknownAccountException -->帐号不存在:");
  29. msg = "UnknownAccountException -->帐号不存在:";
  30. } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
  31. System.out.println("IncorrectCredentialsException -- > 密码不正确:");
  32. msg = "IncorrectCredentialsException -- > 密码不正确:";
  33. } else if ("kaptchaValidateFailed".equals(exception)) {
  34. System.out.println("kaptchaValidateFailed -- > 验证码错误");
  35. msg = "kaptchaValidateFailed -- > 验证码错误";
  36. } else {
  37. msg = "else >> " + exception;
  38. System.out.println("else -- >" + exception);
  39. }
  40. }
  41. map.put("msg", msg);
  42. // 此方法不处理登录成功,由shiro进行处理.
  43. return "/login";
  44. }
  45. }

这时候我们启动应用程序,访问http://127.0.0.1:8080/index

会自动跳转到http://127.0.0.1:8080/login 界面,然后输入账号和密码:admin/123456,这时候会提示:IncorrectCredentialsException -- > 密码不正确。

这主要是因为我们在上面进行了密文的方式,那么怎么加密方式,我们并没有告诉Shiro,所以认证失败了

在这里我们需要编写一个加密算法类,当然Shiro也已经有了具体的实现HashedCredentialsMatcher

我们只需要进行注入使用即可:

在ShiroConfiguration中加入方法:

​[html] 

view plain

 copy

  1. /**
  2. * 凭证匹配器
  3. * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
  4. *  所以我们需要修改下doGetAuthenticationInfo中的代码;
  5. * )
  6. * @return
  7. */
  8. @Bean
  9. public HashedCredentialsMatcher hashedCredentialsMatcher(){
  10. HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
  11. hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
  12. hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
  13. return hashedCredentialsMatcher;
  14. }

在myShiroRealm()方法中注入凭证匹配器:

​[html] 

view plain

 copy

  1. @Bean
  2. public MyShiroRealm myShiroRealm(){
  3. MyShiroRealm myShiroRealm = new MyShiroRealm();
  4. myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());;
  5. return myShiroRealm;
  6. }

这时候在访问/login进行登录就可以登陆到/index界面了。

(d) 权限控制

在我们新建一个UserInfoController

​[html] 

view plain

 copy

  1. package com.example.controller;
  2. import org.apache.shiro.authz.annotation.RequiresPermissions;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. @Controller
  6. @RequestMapping("userInfo")
  7. public class UserInfoController {
  8. /**
  9. * 用户查询.
  10. * @return
  11. */
  12. @RequestMapping("/userList")
  13. public String userInfo(){
  14. return "userInfo";
  15. }
  16. /**
  17. * 用户添加;
  18. * @return
  19. */
  20. @RequestMapping("/userAdd")
  21. public String userInfoAdd(){
  22. return "userInfoAdd";
  23. }
  24. /**
  25. * 用户删除;
  26. * @return
  27. */
  28. @RequestMapping("/userDel")
  29. @RequiresPermissions("userInfo:del")//权限管理;
  30. public String userDel(){
  31. return "userInfoDel";
  32. }
  33. }

然后运行登录进行访问:http://127.0.0.1:8080/userInfo/userAdd

并没有执行doGetAuthorizationInfo()打印信息,所以我们会发现我们的身份认证是好使了,但是权限控制好像没有什么作用哦。

我们少了几部分代码,

第一就是开启shiro aop注解支持,这个只需要在ShiroConfiguration加入如下方法进行开启即可:

​[html] 

view plain

 copy

  1. /**
  2. *  开启shiro aop注解支持.
  3. *  使用代理方式;所以需要开启代码支持;
  4. * @param securityManager
  5. * @return
  6. */
  7. @Bean
  8. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
  9. AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
  10. authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
  11. return authorizationAttributeSourceAdvisor;
  12. }

第二就是在controller方法中加入相应的注解:

​[html] 

view plain

 copy

  1. package com.example.controller;
  2. import org.apache.shiro.authz.annotation.RequiresPermissions;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. @Controller
  6. @RequestMapping("userInfo")
  7. public class UserInfoController {
  8. /**
  9. * 用户查询.
  10. * @return
  11. */
  12. @RequestMapping("/userList")
  13. @RequiresPermissions("userInfo:view")//权限管理;
  14. public String userInfo(){
  15. return "userInfo";
  16. }
  17. /**
  18. * 用户添加;
  19. * @return
  20. */
  21. @RequestMapping("/userAdd")
  22. @RequiresPermissions("userInfo:add")//权限管理;
  23. public String userInfoAdd(){
  24. return "userInfoAdd";
  25. }
  26. /**
  27. * 用户删除;
  28. * @return
  29. */
  30. @RequestMapping("/userDel")
  31. @RequiresPermissions("userInfo:del")//权限管理;
  32. public String userDel(){
  33. return "userInfoDel";
  34. }
  35. }

这时候在访问http://127.0.0.1:8080/userInfo/userAdd 会看到控制台打印信息

​[html] 

view plain

 copy

  1. 权限配置-->MyShiroRealm.doGetAuthorizationInfo()

如果访问:http://127.0.0.1:8080/userInfo/userDel会看到控制台打印信息

​[html] 

view plain

 copy

  1. org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method

​[html] 

view plain

 copy

  1. 页面上看到

​[html] 

view plain

 copy

  1. Whitelabel Error Page
  2. This application has no explicit mapping for /error, so you are seeing this as a fallback.
  3. Sun Aug 28 21:36:31 CST 2016
  4. There was an unexpected error (type=Internal Server Error, status=500).
  5. Subject does not have permission [userInfo:del]

----------------------------------------------

pom.xml

​[html] 

view plain

 copy

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.example</groupId>
  5. <artifactId>spring-boot-shiro1</artifactId>
  6. <version>0.0.1-SNAPSHOT</version>
  7. <packaging>jar</packaging>
  8. <properties>
  9. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  10. </properties>
  11. <!-- Inherit defaults from Spring Boot -->
  12. <parent>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-parent</artifactId>
  15. <version>1.4.0.RELEASE</version>
  16. </parent>
  17. <dependencies>
  18. <!-- spring boot web支持:mvc,aop... -->
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter-web</artifactId>
  22. </dependency>
  23. <!-- thmleaf模板依赖. -->
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  27. </dependency>
  28. <!-- shiro spring. -->
  29. <dependency>
  30. <groupId>org.apache.shiro</groupId>
  31. <artifactId>shiro-spring</artifactId>
  32. <version>1.2.3</version>
  33. </dependency>
  34. <!-- Spirng data JPA依赖; -->
  35. <dependency>
  36. <groupId>org.springframework.boot</groupId>
  37. <artifactId>spring-boot-starter-data-jpa</artifactId>
  38. </dependency>
  39. <!-- 热部署 -->
  40. <dependency>
  41. <groupId>org.springframework.boot</groupId>
  42. <artifactId>spring-boot-devtools</artifactId>
  43. <optional>true</optional>
  44. </dependency>
  45. <!-- mysql驱动; -->
  46. <dependency>
  47. <groupId>mysql</groupId>
  48. <artifactId>mysql-connector-java</artifactId>
  49. </dependency>
  50. </dependencies>
  51. </project>

​[html] 

view plain

 copy

  1. <pre name="code" class="html" style="font-size: 14px; line-height: 28px;">ShiroConfiguration.java

​[html] 

view plain

 copy

  1. package com.example.config.shiro;
  2. import java.util.LinkedHashMap;
  3. import java.util.Map;
  4. import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
  5. import org.apache.shiro.mgt.SecurityManager;
  6. import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
  7. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  8. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  9. import org.springframework.context.annotation.Bean;
  10. import org.springframework.context.annotation.Configuration;
  11. @Configuration
  12. public class ShiroConfiguration {
  13. @Bean
  14. public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
  15. System.out.println("ShiroConfiguration.shiroFilter()");
  16. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  17. // 必须设置SecuritManager
  18. shiroFilterFactoryBean.setSecurityManager(securityManager);
  19. // 拦截器
  20. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
  21. // 配置退出过滤器,其中的具体代码Shiro已经替我们实现了
  22. filterChainDefinitionMap.put("/logout", "logout");
  23. // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
  24. // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
  25. filterChainDefinitionMap.put("/**", "authc");
  26. // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
  27. shiroFilterFactoryBean.setLoginUrl("/login");
  28. // 登录成功后要跳转的链接
  29. shiroFilterFactoryBean.setSuccessUrl("/index");
  30. // 未授权界面;
  31. shiroFilterFactoryBean.setUnauthorizedUrl("/403");
  32. shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
  33. return shiroFilterFactoryBean;
  34. }
  35. @Bean
  36. public SecurityManager securityManager() {
  37. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  38. // 设置realm.
  39. securityManager.setRealm(myShiroRealm());
  40. return securityManager;
  41. }
  42. /**
  43. * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
  44. *
  45. * @return
  46. */
  47. @Bean
  48. public MyShiroRealm myShiroRealm() {
  49. MyShiroRealm myShiroRealm = new MyShiroRealm();
  50. myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
  51. return myShiroRealm;
  52. }
  53. /**
  54. * 凭证匹配器
  55. * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
  56. *  所以我们需要修改下doGetAuthenticationInfo中的代码;
  57. * )
  58. * @return
  59. */
  60. @Bean
  61. public HashedCredentialsMatcher hashedCredentialsMatcher(){
  62. HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
  63. hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
  64. hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
  65. return hashedCredentialsMatcher;
  66. }
  67. /**
  68. *  开启shiro aop注解支持.
  69. *  使用代理方式;所以需要开启代码支持;
  70. * @param securityManager
  71. * @return
  72. */
  73. @Bean
  74. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
  75. AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
  76. authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
  77. return authorizationAttributeSourceAdvisor;
  78. }
  79. }