转:http://412887952-qq-com.iteye.com/blog/2299777

(1). 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/

完整架构图:

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等的依赖:

<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

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>
</head>
<body><h3>index</h3>
</body>
</html>

login.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>
</head>
<body>错误信息:<h4 th:text="${msg}"></h4><form action="" method="post"><p>账号:<input type="text" name="username" value="admin"/></p><p>密码:<input type="text" name="password" value="123456"/></p><p><input type="submit" value="登录"/></p></form>
</body>
</html>

userInfo.html,

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>
</head>
<body><h3>用户查询界面</h3>
</body>
</html>

userInfoAdd.html

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

(d)编写启动类

package com.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

(e)编写HomeController类

新建HomeController类

package com.example.controller;import java.util.Map;import javax.servlet.http.HttpServletRequest;import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;@Controller
public class HomeController {@RequestMapping({ "/", "index" })public String index() {return "/index";}@RequestMapping(value = "/login", method = RequestMethod.GET)public String login() {return "/login";}
}

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

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

(b) 注入Shiro Factory和SecurityManager。

(c) 身份认证

(d) 权限控制

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

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

<!-- shiro spring. --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.2.3</version></dependency>

(b) 注入Shiro Factory和SecurityManager。

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

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

新建ShiroConfiguration.java

package com.example.config.shiro;import java.util.LinkedHashMap;
import java.util.Map;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ShiroConfiguration {@Beanpublic ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {System.out.println("ShiroConfiguration.shiroFilter()");ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 必须设置SecuritManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 拦截器Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// 配置退出过滤器,其中的具体代码Shiro已经替我们实现了filterChainDefinitionMap.put("/logout", "logout");// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->filterChainDefinitionMap.put("/**", "authc");// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面shiroFilterFactoryBean.setLoginUrl("/login");// 登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/index");// 未授权界面;shiroFilterFactoryBean.setUnauthorizedUrl("/403");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}@Beanpublic SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();return securityManager;}}

这里说下: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进行自动生成。

<!-- Spirng data JPA依赖; --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- mysql驱动; --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>

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

########################################################
###datasource
########################################################
spring.datasource.url = jdbc:mysql://localhost:3306/test
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.max-active=20
spring.datasource.max-idle=8
spring.datasource.min-idle=8
spring.datasource.initial-size=10########################################################
### Java Persistence Api
########################################################
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
#[org.hibernate.cfg.ImprovedNamingStrategy | org.hibernate.cfg.DefaultNamingStrategy]
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

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

UserInfo.java

package com.example.domain;import java.io.Serializable;
import java.util.List;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;/*** 用户信息.* @author Administrator**/
@Entity
public class UserInfo implements Serializable {/*** */private static final long serialVersionUID = 1L;@Id@GeneratedValueprivate long uid;// 用户id@Column(unique = true)private String username;// 帐号private String name;// 名称(昵称或者真实姓名,不同系统不同定义)private String password; // 密码;private String salt;// 加密密码的盐private byte state;// 用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 ,// 1:正常状态,2:用户被锁定.@ManyToMany(fetch = FetchType.EAGER) // 立即从数据库中进行加载数据@JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns = {@JoinColumn(name = "roleId") })private List<SysRole> roleList;// 一个用户具有多个角色public long getUid() {return uid;}public void setUid(long uid) {this.uid = uid;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getSalt() {return salt;}public void setSalt(String salt) {this.salt = salt;}public byte getState() {return state;}public void setState(byte state) {this.state = state;}public List<SysRole> getRoleList() {return roleList;}public void setRoleList(List<SysRole> roleList) {this.roleList = roleList;}/*** 密码盐.* * @return*/public String getCredentialsSalt() {return this.username + this.salt;}@Overridepublic String toString() {return "UserInfo [uid=" + uid + ", username=" + username + ", name=" + name + ", password=" + password+ ", salt=" + salt + ", state=" + state + "]";}}

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

getCredentialsSalt()

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

SysRole.java

package com.example.domain;import java.io.Serializable;
import java.util.List;import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;/*** 系统角色实体类;* * @author Administrator**/
@Entity
public class SysRole implements Serializable {private static final long serialVersionUID = 1L;@Id@GeneratedValueprivate Long id; // 编号private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:private String description; // 角色描述,UI界面显示使用private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户// 角色 -- 权限关系:多对多关系;@ManyToMany(fetch = FetchType.EAGER)@JoinTable(name = "SysRolePermission", joinColumns = { @JoinColumn(name = "roleId") }, inverseJoinColumns = {@JoinColumn(name = "permissionId") })private List<SysPermission> permissions;// 用户 - 角色关系定义;@ManyToMany@JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "roleId") }, inverseJoinColumns = {@JoinColumn(name = "uid") })private List<UserInfo> userInfos;// 一个角色对应多个用户public List<UserInfo> getUserInfos() {return userInfos;}public void setUserInfos(List<UserInfo> userInfos) {this.userInfos = userInfos;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getRole() {return role;}public void setRole(String role) {this.role = role;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}public Boolean getAvailable() {return available;}public void setAvailable(Boolean available) {this.available = available;}public List<SysPermission> getPermissions() {return permissions;}public void setPermissions(List<SysPermission> permissions) {this.permissions = permissions;}@Overridepublic String toString() {return "SysRole [id=" + id + ", role=" + role + ", description=" + description + ", available=" + available+ ", permissions=" + permissions + "]";}
}

SysPermission.java

package com.example.domain;import java.io.Serializable;
import java.util.List;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;/*** 权限实体类;* */
@Entity
public class SysPermission implements Serializable {private static final long serialVersionUID = 1L;@Id@GeneratedValueprivate long id;// 主键.private String name;// 名称.@Column(columnDefinition = "enum('menu','button')")private String resourceType;// 资源类型,[menu|button]private String url;// 资源路径.private String permission; // 权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:viewprivate Long parentId; // 父编号private String parentIds; // 父编号列表private Boolean available = Boolean.FALSE;//    @ManyToMany(fetch = FetchType.LAZY)
//  @JoinTable(name = "SysRolePermission", joinColumns = { @JoinColumn(name = "permissionId") }, inverseJoinColumns = {
//          @JoinColumn(name = "roleId") })
//  private List<SysRole> roles;public long getId() {return id;}public void setId(long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getResourceType() {return resourceType;}public void setResourceType(String resourceType) {this.resourceType = resourceType;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getPermission() {return permission;}public void setPermission(String permission) {this.permission = permission;}public Long getParentId() {return parentId;}public void setParentId(Long parentId) {this.parentId = parentId;}public String getParentIds() {return parentIds;}public void setParentIds(String parentIds) {this.parentIds = parentIds;}public Boolean getAvailable() {return available;}public void setAvailable(Boolean available) {this.available = available;}//    public List<SysRole> getRoles() {
//      return roles;
//  }
//
//  public void setRoles(List<SysRole> roles) {
//      this.roles = roles;
//  }@Overridepublic String toString() {return "SysPermission [id=" + id + ", name=" + name + ", resourceType=" + resourceType + ", url=" + url+ ", permission=" + permission + ", parentId=" + parentId + ", parentIds=" + parentIds + ", available="+ available + "]";}}

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

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

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

package com.example.repository;import org.springframework.data.repository.CrudRepository;import com.example.domain.UserInfo;/*** UserInfo持久化类* * @author Administrator**/
public interface UserInfoRepository extends CrudRepository<UserInfo, Long> {/** 通过username查找用户信息 **/public UserInfo findByUsername(String username);}

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

编写一个业务处理类UserInfoService>

package com.example.service;import com.example.domain.UserInfo;public interface UserInfoService {public UserInfo findByUsername(String username);}
package com.example.service.impl;import javax.annotation.Resource;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import com.example.domain.UserInfo;
import com.example.repository.UserInfoRepository;
import com.example.service.UserInfoService;@Service
public class UserInfoServiceImpl implements UserInfoService{@Resourceprivate UserInfoRepository userInfoRepository;@Transactional(readOnly=true)@Overridepublic UserInfo findByUsername(String username) {System.out.println("UserInfoServiceImpl.findByUsername()");return userInfoRepository.findByUsername(username);}}

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

package com.example.config.shiro;import javax.annotation.Resource;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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.context.annotation.Bean;import com.example.domain.SysPermission;
import com.example.domain.SysRole;
import com.example.domain.UserInfo;
import com.example.service.UserInfoService;/*** 身份校验核心类* * @author Administrator**/
public class MyShiroRealm extends AuthorizingRealm {@Resourceprivate UserInfoService userInfoService;/*** 认证信息(身份验证) Authentication 是用来验证用户身份*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("MyShiroRealm.doGetAuthenticationInfo()");// 获取用户的输入帐号String username = (String) token.getPrincipal();System.out.println(token.getCredentials());// 通过username从数据库中查找 User对象,如果找到,没找到.// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法UserInfo userInfo = userInfoService.findByUsername(username);System.out.println("----->>userInfo=" + userInfo);if (userInfo == null) {return null;}SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 用户名userInfo.getPassword(), // 密码ByteSource.Util.bytes(userInfo.getCredentialsSalt()), // salt=username+saltgetName() // realm name);return authenticationInfo;}@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// TODO Auto-generated method stubSystem.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();for(SysRole role:userInfo.getRoleList()){authorizationInfo.addRole(role.getRole());System.out.println(role.getPermissions());for(SysPermission p:role.getPermissions()){System.out.println(p);authorizationInfo.addStringPermission(p.getPermission());}}return authorizationInfo;}}

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

doGetAuthenticationInfo();

doGetAuthorizationInfo();

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

SimpleAuthenticationInfoauthenticationInfo =new SimpleAuthenticationInfo(userInfo, //用户名userInfo.getPassword(), //密码ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+saltgetName()  //realm name);

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

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

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

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

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

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

authorizationInfo.addRole(role.getRole());
authorizationInfo.addStringPermission(p.getPermission());
当然也可以添加集合:

authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(stringPermissions);

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

在ShiroConfiguration.java中添加方法

/*** 身份认证realm;* */@Beanpublic MyShiroRealm myShiroRealm(){MyShiroRealm myShiroRealm = new MyShiroRealm();return myShiroRealm;}

将myShiroRealm注入到securityManager中:

@Beanpublic SecurityManager securityManager(){DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();//设置realm.securityManager.setRealm(myShiroRealm());return securityManager;}

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

package com.example.controller;import java.util.Map;import javax.servlet.http.HttpServletRequest;import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;@Controller
public class HomeController {@RequestMapping({ "/", "index" })public String index() {return "/index";}@RequestMapping(value = "/login", method = RequestMethod.GET)public String login() {return "/login";}@RequestMapping(value = "/login", method = RequestMethod.POST)public String login(HttpServletRequest request, Map<String, Object> map) {System.out.println("HomeController.login");// 登录失败从request中获取shiro处理的异常信息// shiroLoginFailure:就是shiro异常类的全类名String exception = (String) request.getAttribute("shiroLoginFailure");String msg = "";if (exception != null) {if (UnknownAccountException.class.getName().equals(exception)) {System.out.println("UnknownAccountException -->帐号不存在:");msg = "UnknownAccountException -->帐号不存在:";} else if (IncorrectCredentialsException.class.getName().equals(exception)) {System.out.println("IncorrectCredentialsException -- > 密码不正确:");msg = "IncorrectCredentialsException -- > 密码不正确:";} else if ("kaptchaValidateFailed".equals(exception)) {System.out.println("kaptchaValidateFailed -- > 验证码错误");msg = "kaptchaValidateFailed -- > 验证码错误";} else {msg = "else >> " + exception;System.out.println("else -- >" + exception);}}map.put("msg", msg);// 此方法不处理登录成功,由shiro进行处理.return "/login";}
}

这时候我们启动应用程序,访问http://127.0.0.1:8080/index
会自动跳转到http://127.0.0.1:8080/login 界面,然后输入账号和密码:admin/123456,这时候会提示:IncorrectCredentialsException -- > 密码不正确。
这主要是因为我们在上面进行了密文的方式,那么怎么加密方式,我们并没有告诉Shiro,所以认证失败了

在这里我们需要编写一个加密算法类,当然Shiro也已经有了具体的实现HashedCredentialsMatcher
我们只需要进行注入使用即可:
在ShiroConfiguration中加入方法:

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

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

@Beanpublic MyShiroRealm myShiroRealm(){MyShiroRealm myShiroRealm = new MyShiroRealm();myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());;return myShiroRealm;}

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

(d) 权限控制
在我们新建一个UserInfoController

package com.example.controller;import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
@RequestMapping("userInfo")
public class UserInfoController {/*** 用户查询.* @return*/@RequestMapping("/userList")public String userInfo(){return "userInfo";}/*** 用户添加;* @return*/@RequestMapping("/userAdd")public String userInfoAdd(){return "userInfoAdd";}/*** 用户删除;* @return*/@RequestMapping("/userDel")@RequiresPermissions("userInfo:del")//权限管理;public String userDel(){return "userInfoDel";}
}

然后运行登录进行访问:http://127.0.0.1:8080/userInfo/userAdd
并没有执行doGetAuthorizationInfo()打印信息,所以我们会发现我们的身份认证是好使了,但是权限控制好像没有什么作用哦。

我们少了几部分代码,

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

/***  开启shiro aop注解支持.*  使用代理方式;所以需要开启代码支持;* @param securityManager* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}

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

package com.example.controller;import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
@RequestMapping("userInfo")
public class UserInfoController {/*** 用户查询.* @return*/@RequestMapping("/userList")@RequiresPermissions("userInfo:view")//权限管理;public String userInfo(){return "userInfo";}/*** 用户添加;* @return*/@RequestMapping("/userAdd")@RequiresPermissions("userInfo:add")//权限管理;public String userInfoAdd(){return "userInfoAdd";}/*** 用户删除;* @return*/@RequestMapping("/userDel")@RequiresPermissions("userInfo:del")//权限管理;public String userDel(){return "userInfoDel";}
}

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

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

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

org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method
页面上看到
Whitelabel Error PageThis application has no explicit mapping for /error, so you are seeing this as a fallback.Sun Aug 28 21:36:31 CST 2016
There was an unexpected error (type=Internal Server Error, status=500).
Subject does not have permission [userInfo:del]

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

pom.xml

<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><!-- shiro spring. --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.2.3</version></dependency><!-- Spirng data JPA依赖; --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- 热部署 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency><!-- mysql驱动; --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency></dependencies>
</project>
<pre name="code" class="html" style="font-size: 14px; line-height: 28px;">ShiroConfiguration.java
package com.example.config.shiro;import java.util.LinkedHashMap;
import java.util.Map;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ShiroConfiguration {@Beanpublic ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {System.out.println("ShiroConfiguration.shiroFilter()");ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 必须设置SecuritManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 拦截器Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// 配置退出过滤器,其中的具体代码Shiro已经替我们实现了filterChainDefinitionMap.put("/logout", "logout");// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->filterChainDefinitionMap.put("/**", "authc");// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面shiroFilterFactoryBean.setLoginUrl("/login");// 登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/index");// 未授权界面;shiroFilterFactoryBean.setUnauthorizedUrl("/403");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}@Beanpublic SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 设置realm.securityManager.setRealm(myShiroRealm());return securityManager;}/*** 身份认证realm; (这个需要自己写,账号密码校验;权限等)* * @return*/@Beanpublic MyShiroRealm myShiroRealm() {MyShiroRealm myShiroRealm = new MyShiroRealm();myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());return myShiroRealm;}/*** 凭证匹配器* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了*  所以我们需要修改下doGetAuthenticationInfo中的代码;* )* @return*/@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher(){HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));return hashedCredentialsMatcher;}/***  开启shiro aop注解支持.*  使用代理方式;所以需要开启代码支持;* @param securityManager* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}}

Spring Boot Shiro权限管理相关推荐

  1. (39.3) Spring Boot Shiro权限管理【从零开始学Spring Boot】

    在学习此小节之前您可能还需要学习: (39.1) Spring Boot Shiro权限管理[从零开始学Spring Boot] http://412887952-qq-com.iteye.com/b ...

  2. Spring Boot Shiro 权限管理

    Spring Boot Shiro 权限管理 标签: springshiro 2016-01-14 23:44 94587人阅读 评论(60) 收藏 举报 本来是打算接着写关于数据库方面,集成MyBa ...

  3. 39 Spring Boot Shiro权限管理【从零开始学Spring Boot】

    [视频 & 交流平台] à SpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008& ...

  4. Spring Boot Shiro权限管理--自定义 FormAuthenticationFilter验证码整合

    思路 shiro使用FormAuthenticationFilter进行表单认证,验证校验的功能应该加在FormAuthenticationFilter中,在认证之前进行验证码校验. 需要写FormA ...

  5. Spring +mybatisplus+shiro权限管理集成整合

    一.Apache Shiro是一个功能强大.灵活的,开源的安全框架.它可以干净利落地处理身份验证.授权.企业会话管理和加密. Shiro能做什么呢? 验证用户身份 用户访问权限控制,比如:1.判断用户 ...

  6. spring整合shiro权限管理与数据库设计

    之前的文章中我们完成了基础框架的搭建,现在基本上所有的后台系统都逃不过权限管理这一块,这算是一个刚需了.现在我们来集成shiro来达到颗粒化权限管理,也就是从连接菜单到页面功能按钮,都进行权限都验证, ...

  7. SpringBoot系列 - 集成Shiro权限管理

    Apache Shiro是Java的一个安全框架.目前,使用Apache Shiro的人越来越多,相比Spring Security而言相当简单, 可能没有Spring Security做的功能强大, ...

  8. Spring Boot Shiro视频 - 身份认证准备工作

    [视频 & 交流平台] à SpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008& ...

  9. SSM集成shiro权限管理

    这几天在学习了shiro权限管理框架,在刚开始的时候学的时候因为这个配置问题困扰了我很长时间,所以在这篇文章我整合了自己用SSM搭建shiro权限框架的过程. 1.配置 1.1jar包 在项目配置开始 ...

最新文章

  1. phpstorm配置Xdebug进行调试PHP教程
  2. /usr/bin被删除的操作
  3. 分布式入门:常用的分布式基础算法
  4. 在mybatis用mysql的代码块_关于Mybatis 中使用Mysql存储过程的方法
  5. 154. Find Minimum in Rotated Sorted Array II
  6. 操作系统(二): 进程与线程
  7. matlab自带的人脸分类器,基于MATLAB,运用PCA+SVM的特征脸方法人脸识别
  8. vim编辑器常见使用
  9. c语言入门经典课后作业,C语言入门经典习题答案.doc
  10. 转自JavaEye --Oracle函数大全
  11. @RequestMapping测试各种访问方式
  12. 盘点愚人节各大网站彩蛋,谁最爱恶搞?
  13. 如何美化菜单界面java_用 Java 创建带图像的菜单来美化界面
  14. linux-什么是Linux系统?linux详解Linux与Windows的区别Linux发行版本及特点介绍
  15. 别人犯错给自己的警醒(二):人取得成就之后很容易膨胀
  16. 如何在网页中播放音乐和视频
  17. Android UI线程
  18. 删除node_modules慢【rimraf】
  19. git 提交时报错 error: failed to push some refs to ‘https://github.com/xxx/demo.git 解决方法
  20. 阿里P7爆款《K8s+Jenkins》技术笔记,读了后确实有实质性的帮助

热门文章

  1. 五款开源内容管理平台对比(免费部分):bolo/fastadmin/wordpress/zblog/redmine
  2. RabbitMQ学习笔记:集群和网络分区(Network Partitions)
  3. 浅谈XSS跨站脚本攻击
  4. 富文本生成pdf-java
  5. 工作中的高效工具推荐,职场人士必备便签小工具
  6. 访问学者如何申请美国J1签证?
  7. Python-Python与数据库
  8. PyTorch学习笔记(20) ——激活函数
  9. Halide::Generator生成器使用说明
  10. 微信小程序实现本地存储