`

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

阅读更多

 à悟空学院:https://t.cn/Rg3fKJD

学院中有Spring Boot相关的课程!点击「阅读原文」进行查看!

SpringBoot视频:http://t.cn/A6ZagYTi

Spring Cloud视频:http://t.cn/A6ZagxSR

SpringBoot Shiro视频:http://t.cn/A6Zag7IV

SpringBoot交流平台:https://t.cn/R3QDhU0

SpringData和JPA视频:http://t.cn/A6Zad1OH

SpringSecurity5.0视频:http://t.cn/A6ZadMBe

Sharding-JDBC分库分表实战http://t.cn/A6ZarrqS

分布式事务解决方案「手写代码」:http://t.cn/A6ZaBnIr

 

 

本节提供源代码,在最下面可以下载

(4). 集成Shiro 进行用户授权

在看此小节前,您可能需要先看:

http://412887952-qq-com.iteye.com/blog/2299732   

 

 

64.JPA命名策略【从零开始学Spring Boot】 编辑

 

139. Spring Boot Shiro登录成功之后下载favicon.ico

146. Spring Boot Shiro无法访问JS/CSS/IMG+自定义Filter无法访问完美方案【从零开始学Spring Boot】

 

紧着上一小节,在上一节我们编写了简单的一个小程序,但是我们会发现我们随便访问index,login 以及任何一个界面,无需登录也可以进行访问,但是这不是我们所想要的,我们想要的是希望在用户没有登录的情况下,跳转login页面进行登录。那么这个时候Shiro就闪亮登场了。

      集成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.2</version>
       </dependency>
 

 

 

(b) 注入Shiro FactorySecurityManager:

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

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

新建类 com.kfit.config.shiro.ShiroConfiguration

 

package com.kfit.config.shiro;
 
import java.util.LinkedHashMap;
import java.util.Map;
 
import org.apache.shiro.mgt.SecurityManager;
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;
 
/**
 * Shiro 配置
 *
Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。
既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
 *
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
@Configuration
public class ShiroConfiguration {
      
   
    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     *
        Filter Chain定义说明
       1、一个URL可以配置多个Filter,使用逗号分隔
       2、当设置多个过滤器时,全部验证通过,才视为通过
       3、部分过滤器可指定参数,如perms,roles
     *
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
       System.out.println("ShiroConfiguration.shirFilter()");
       ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();
      
        // 必须设置 SecurityManager 
       shiroFilterFactoryBean.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);
       returnshiroFilterFactoryBean;
    }
   
   
    @Bean
    public 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执行,这时会调用RealmgetAuthenticationInfo(token)方法。

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

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

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

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

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

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

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

 

 

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

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

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

 

       <!-- 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.javaSysRole.javaSysPermission.java至于之前的关联表我们使用JPA进行自动生成。

用户:com.kfit.core.bean.UserInfo

 

package com.kfit.core.bean;
 
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 Angel(QQ:412887952)
 * @version v.0.1
 */
@Entity
public class UserInfo implements Serializable{
    private static final long serialVersionUID = 1L;
    @Id@GeneratedValue
    privatelonguid;//用户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 List<SysRole> getRoleList() {
       return roleList;
    }
 
    public void setRoleList(List<SysRole> roleList) {
       this.roleList = roleList;
    }
 
    public long getUid() {
       return uid;
    }
 
    public void setUid(longuid) {
       this.uid = uid;
    }
 
    public String getUsername() {
       return username;
    }
 
    public void setUsername(String username) {
       this.username = username;
    }
 
    public String getName() {
       return name;
    }
 
    publicvoid 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(bytestate) {
       this.state = state;
    }
 
    /**
     * 密码盐.
     * @return
     */
    public String getCredentialsSalt(){
       return this.username+this.salt;
    }
 
    @Override
    public String toString() {
       return "UserInfo [uid=" + uid + ", username=" + username + ", name=" + name + ", password=" + password
              + ", salt=" + salt + ", state=" + state + "]";
    }
 
   
}
 

 

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

 

getCredentialsSalt()

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

 

角色类 > com.kfit.core.bean.SysRole

 

package com.kfit.core.bean;
 
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 Angel(QQ:412887952)
 * @version v.0.1
 */
@Entity
public class SysRole implements Serializable{
    private static final long serialVersionUID = 1L;
    @Id@GeneratedValue
    private 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;
    }
    publicvoid 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;
    }
    @Override
    public String toString() {
       return "SysRole [id=" + id + ", role=" + role + ", description=" + description + ", available=" + available
              + ", permissions=" + permissions + "]";
    }
}
 

 

 

权限 > com.kfit.core.bean.SysPermission

 

package com.kfit.core.bean;
 
import java.io.Serializable;
import java.util.List;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
 
/**
 * 权限实体类;
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
@Entity
public class SysPermission implements Serializable{
    private static final long serialVersionUID = 1L;
   
    @Id@GeneratedValue
    privatelongid;//主键.
    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:view
    private Long parentId; //父编号
    private String parentIds; //父编号列表
    private Boolean available = Boolean.FALSE;
   
    @ManyToMany
@JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
    private List<SysRole> roles;
   
    public long getId() {
       return id;
    }
    public void setId(longid) {
       this.id = id;
    }
    public String getName() {
       return name;
    }
    publicvoid 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;
    }
    publicvoid setRoles(List<SysRole> roles) {
       this.roles = roles;
    }
    @Override
    public String toString() {
       return "SysPermission [id=" + id + ", name=" + name + ", resourceType=" + resourceType + ", url=" + url
              + ", permission=" + permission + ", parentId=" + parentId + ", parentIds=" + parentIds + ", available="
              + available + ", roles=" + roles + "]";
    }
   
}
 

 

ok,到这里实体类就编码完毕了,在这里我们看到的是3个实体类,UserInfo,SysRole,SysPermission,对应的是数据库的五张表:

1UserInfo2SysUserRole3SysRole4SysRolePermission5SysPermission

这时候运行程序,就会自动建表,然后我们添加一些数据:

 

INSERT INTO `SysPermission` VALUES ('1', '1', '用户管理', '0', '0/', 'userInfo:view', 'menu', 'userInfo/userList');
INSERT INTO `SysPermission` VALUES ('2', '1', '用户添加', '1', '0/1', 'userInfo:add', 'button', 'userInfo/userAdd');
INSERT INTO `SysPermission` VALUES ('3', '1', '用户删除', '1', '0/1', 'userInfo:del', 'button', 'userInfo/userDel');

 

 

 

INSERT INTO `SysRole` VALUES ('1', '1', '管理员', 'admin');
INSERT INTO `SysRole` VALUES ('2', '1', 'VIP会员', 'vip');
 

 

 

 

INSERT INTO `SysRolePermission` VALUES ('1', '1');
INSERT INTO `SysRolePermission` VALUES ('1', '2');
 

 

 

 

 

INSERT INTO `SysUserRole` VALUES ('1', '1');
INSERT INTO `SysUserRole` VALUES ('1', '2');
 

 

 

 

INSERT INTO `UserInfo` VALUES ('1', '管理员', 'admin', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', '0');
  

 

 

这时候数据都准备完毕了,那么接下来就应该编写Repository进行访问数据了(在下载源码中有shiro.sql文件,可自行导入使用即可)。

com.kfit.core.repository.UserInfoRepository

 

package com.kfit.core.repository;
 
import org.springframework.data.repository.CrudRepository;
 
import com.kfit.core.bean.UserInfo;
 
/**
 * UserInfo持久化类;
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
public interface UserInfoRepository extends CrudRepository<UserInfo,Long>{
   
    /**通过username查找用户信息;*/
    public UserInfo findByUsername(String username);
   
}
 

 

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

编写一个业务处理类UserInfoService>

 

com.kfit.core.service.UserInfoService :

 

package com.kfit.core.service;
 
import com.kfit.core.bean.UserInfo;
 
public interface UserInfoService {
   
    /**通过username查找用户信息;*/
    public UserInfo findByUsername(String username);
   
}
 

 

 

com.kfit.core.service.impl.UserInfoServiceImpl :

 

package com.kfit.core.service.impl;
 
import javax.annotation.Resource;
 
import org.springframework.stereotype.Service;
 
import com.kfit.core.bean.UserInfo;
import com.kfit.core.repository.UserInfoRepository;
import com.kfit.core.service.UserInfoService;
 
@Service
public class UserInfoServiceImpl implements UserInfoService{
   
    @Resource
    private UserInfoRepository userInfoRepository;
   
    @Override
    public UserInfo findByUsername(String username) {
       System.out.println("UserInfoServiceImpl.findByUsername()");
       return userInfoRepository.findByUsername(username);
    }
   
}
 

 

这里主要是为了满足MVC编程模式,在例子中直接访问DAO层也未尝不可,实际开发中建议还是遵循MVC开发模式,至于有什么好处,请自行百度MVC

      好了以上都是为了实现身份认证,权限控制的准备工作,想必大家看了也有点困惑了,坚持住,这个技术点不是每个人都能进行编码的,你会发现在一个公司里都是技术领导帮你实现了这一部分很复杂的编码,你只需要通过界面进行配置而已,所以嘛,要做一个技术领导这一部分不掌握好像还不行了。好了,说重点吧,基本工作准备好之后,剩下的才是重点,shiro的认证最终是交给了Realm进行执行了,所以我们需要自己重新实现一个Realm,此Realm继承AuthorizingRealm

com.kfit.config.shiro.MyShiroRealm

 

package com.kfit.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.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.apache.shiro.util.ByteSource;
 
import com.kfit.core.bean.SysPermission;
import com.kfit.core.bean.SysRole;
import com.kfit.core.bean.UserInfo;
import com.kfit.core.service.UserInfoService;
 
/**
 *  身份校验核心类;
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
public class MyShiroRealm extends AuthorizingRealm{
 
       
    @Resource
    private UserInfoService userInfoService;
   
    /**
     * 认证信息.(身份验证)
     * :
     * Authentication 是用来验证用户身份
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected 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;
       }
      
       /*
        * 获取权限信息:这里没有进行实现,
        * 请自行根据UserInfo,Role,Permission进行实现;
        * 获取之后可以在前端for循环显示所有链接;
        */
       //userInfo.setPermissions(userService.findPermissions(user));
      
      
       //账号判断;
      
       //加密方式;
       //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
       SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
              userInfo, //用户名
              userInfo.getPassword(), //密码
                ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
                getName()  //realm name
        );
      
        //明文: 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
//      SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
//           userInfo, //用户名
//           userInfo.getPassword(), //密码
//             getName()  //realm name
//      );
      
       return authenticationInfo;
    }
   
   
   
    /**
     * 此方法调用  hasRole,hasPermission的时候才会进行回调.
     *
     * 权限信息.(授权):
     * 1、如果用户正常退出,缓存自动清空;
     * 2、如果用户非正常退出,缓存自动清空;
     * 3、如果我们修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。
     * (需要手动编程进行实现;放在service进行调用)
     * 在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例,
     * 调用clearCached方法;
     * :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
       /*
        * 当没有使用缓存的时候,不断刷新页面的话,这个代码会不断执行,
        * 当其实没有必要每次都重新设置权限信息,所以我们需要放到缓存中进行管理;
        * 当放到缓存中时,这样的话,doGetAuthorizationInfo就只会执行一次了,
        * 缓存过期之后会再次执行。
        */
       System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
      
       SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
       UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal();
      
       //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
//     UserInfo userInfo = userInfoService.findByUsername(username)
      
      
       //权限单个添加;
       // 或者按下面这样添加
        //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色   
//     authorizationInfo.addRole("admin"); 
        //添加权限 
//     authorizationInfo.addStringPermission("userInfo:query");
      
      
      
       ///在认证成功之后返回.
       //设置角色信息.
       //支持 Set集合,
       //用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的四行可以不要
//        List<Role> roleList=user.getRoleList();
//        for (Role role : roleList) {
//            info.addStringPermissions(role.getPermissionsName());
//        }
       for(SysRole role:userInfo.getRoleList()){
           authorizationInfo.addRole(role.getRole());
           for(SysPermission p:role.getPermissions()){
              authorizationInfo.addStringPermission(p.getPermission());
           }
       }
      
       //设置权限信息.
//     authorizationInfo.setStringPermissions(getStringPermissions(userInfo.getRoleList()));
      
       return authorizationInfo;
    }
   
   
    /**
     * 将权限对象中的权限code取出.
     * @param permissions
     * @return
     */
//  public Set<String> getStringPermissions(Set<SysPermission> permissions){
//     Set<String> stringPermissions = new HashSet<String>();
//     if(permissions != null){
//         for(SysPermission p : permissions) {
//            stringPermissions.add(p.getPermission());
//          }
//     }
//       return stringPermissions;
//  }
   
}
 

 

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

 

 

doGetAuthenticationInfo();

doGetAuthorizationInfo();

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

 

 

SimpleAuthenticationInfoauthenticationInfo =
 new SimpleAuthenticationInfo(
                userInfo, //用户名
                userInfo.getPassword(), //密码
                ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
                getName()  //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中。

com.kfit.config.shiro.ShiroConfiguration中添加方法:

 

/**
     * 身份认证realm;
     * (这个需要自己写,账号密码校验;权限等)
     * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm(){
       MyShiroRealm myShiroRealm = new MyShiroRealm();
       return myShiroRealm;
    }
 

 

myShiroRealm注入到securityManager中:

 

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

 

 

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

com.kfit.root.controller.HomeController中添加login post处理:

 

// 登录提交地址和applicationontext-shiro.xml配置的loginurl一致。 (配置文件方式的说法)
    @RequestMapping(value="/login",method=RequestMethod.POST)
    public String login(HttpServletRequest request, Map<String, Object> map) throws Exception {
       System.out.println("HomeController.login()");
       // 登录失败从request中获取shiro处理的异常信息。
       // shiroLoginFailure:就是shiro异常类的全类名.
       String exception = (String) request.getAttribute("shiroLoginFailure");
 
       System.out.println("exception=" + exception);
       String msg = "";
       if (exception != null) {
           if (UnknownAccountException.class.getName().equals(exception)) {
              System.out.println("UnknownAccountException -- > 账号不存在:");
              msg = "UnknownAccountException -- > 账号不存在:";
           } elseif (IncorrectCredentialsException.class.getName().equals(exception)) {
              System.out.println("IncorrectCredentialsException -- > 密码不正确:");
              msg = "IncorrectCredentialsException -- > 密码不正确:";
           } elseif ("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

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

com.kfit.config.shiro.ShiroConfiguration中加入方法:

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

 

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

/**
     * 身份认证realm;
     * (这个需要自己写,账号密码校验;权限等)
     * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm(){
       MyShiroRealm myShiroRealm = new MyShiroRealm();
       myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());;
       return myShiroRealm;
    }

 

 

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

这一节就先到这里吧,实在是有点头大了是吧。到时候奉上源代码。

 

(d) 权限控制

      在上一小节我们已经可以登录了。

在我们新建一个UserInfoController

com.kfit.core.controller.UserInfoController

package com.kfit.core.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";
    }
   
}

 

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

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

我们少了几部分代码,

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

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

 

 

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

/**
     * 用户添加;
     * @return
     */
    @RequestMapping("/userAdd")
    @RequiresPermissions("userInfo:add")//权限管理;
    public String userInfoAdd(){
       return "userInfoAdd";
    }

 

 

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

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

如果访问:http://127.0.0.1:8080/userInfo/userDel 会看到

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat May 21 22:17:55 CST 2016
There was an unexpected error (type=Internal Server Error, status=500).
Subject does not have permission [userInfo:del]

 

当然我们需要在UserInfoController方法中加入:

/**
     * 用户删除;
     * @return
     */
    @RequestMapping("/userDel")
    @RequiresPermissions("userInfo:del")//权限管理;
    public String userDel(){
       return "userInfoDel";
    }

 

在上面的错误信息中Subject does not have permission可以看出此用户没有这个权限。好了,至此Shiro的权限控制到此先告一段落。在这里我先抛出一个问题:我们不断的访问http://127.0.0.1:8080/userInfo/userAdd 你会看到

权限配置-->MyShiroRealm.doGetAuthorizationInfo()
2016-05-21 22:20:26.263  INFO 11692 --- [nio-8080-exec-1] org.apache.shiro.realm.AuthorizingRealm  : No cache or cacheManager properties have been set.  Authorization cache cannot be obtained.
权限配置-->MyShiroRealm.doGetAuthorizationInfo()
2016-05-21 22:20:26.385  INFO 11692 --- [nio-8080-exec-2] org.apache.shiro.realm.AuthorizingRealm  : No cache or cacheManager properties have been set.  Authorization cache cannot be obtained.
权限配置-->MyShiroRealm.doGetAuthorizationInfo()
2016-05-21 22:20:26.538  INFO 11692 --- [nio-8080-exec-3] org.apache.shiro.realm.AuthorizingRealm  : No cache or cacheManager properties have been set.  Authorization cache cannot be obtained.
权限配置-->MyShiroRealm.doGetAuthorizationInfo()

 

这说明我们不断的访问权限信息,但是实际中我们的权限信息是不怎么会改变的,所以我们希望是第一次访问,然后进行缓存处理,那么Shiro是否支持呢,答案是肯定的,我们在下一小节进行讲解,如何在Shiro中加入缓存机制。

 

139. Spring Boot Shiro登录成功之后下载favicon.ico

 

 【视频&交流平台】

à悟空学院:https://t.cn/Rg3fKJD

学院中有Spring Boot相关的课程!点击「阅读原文」进行查看!

SpringBoot视频:http://t.cn/A6ZagYTi

Spring Cloud视频:http://t.cn/A6ZagxSR

SpringBoot Shiro视频:http://t.cn/A6Zag7IV

SpringBoot交流平台:https://t.cn/R3QDhU0

SpringData和JPA视频:http://t.cn/A6Zad1OH

SpringSecurity5.0视频:http://t.cn/A6ZadMBe

Sharding-JDBC分库分表实战http://t.cn/A6ZarrqS

分布式事务解决方案「手写代码」:http://t.cn/A6ZaBnIr

 

à Spring Boot交流平台

http://412887952-qq-com.iteye.com/blog/2321532

 

 ======================================

Spring Boot Shiro视频实战篇【已更新】

======================================

 

适合人群

有Spring Boot基础的人群。

 

使用技术

(1)spring boot(整合框架)

(2)spring mvc

(3)spring data jpa(持久化操作)

(4)shiro(安全框架)

(5)thymeleaf(模板引擎)

(6)ehcache(缓存管理)

(7)mysql(数据库)

(8)js/css/img(静态资源使用)

9kaptcha(验证码库)

 

课程目录

1. Spring Boot Shiro介绍

 

2. Spring Boot 搭建无Shiro的框架

 

3. Spring Boot Shiro拦截

 

4. Spring Boot Shiro身份认证准备工作

 

5. Spring Boot Shiro身份认证

 

6. Spring Boot Shiro权限控制

 

7. Spring Boot Shiro缓存

 

8. Spring Boot Shiro记住密码

 

9. Spring Boot Shiro登录成功之后下载favicon.ico

 

10. Spring Boot 在thymeleaf使用shiro标签

 

11. Spring Boot Shiro密码加密算法

 

12.Spring Boot Shiro使用JS-CSS-IMG

 

13. Spring Boot Shiro限制登录尝试次数

 

14.Spring Boot Shiro 验证码

 

更多查看博客: http://412887952-qq-com.iteye.com/

 

 

11
2
分享到:
评论
38 楼 lygwtwith 2017-08-25  
林祥纤 写道
Kaiser8_Li 写道
登陆页面点登陆会下载favicon.icon ,页面也没跳转到index.html。这是什么原因,谁能告诉我下。。。代码是一摸一样的。用的浏览器是谷歌


还有人碰到嘛?

我这也是这个问题
37 楼 林祥纤 2017-08-18  
u013990690 写道
纤哥,我在从login跳转到index页面时报错:Authentication failed for token submission [org.apache.shiro.authc.UsernamePasswordToken

java.lang.NullPointerException: null 说是null,不明白这个null是哪个?


你打印下是哪个信息为null了。
36 楼 u013990690 2017-08-18  
纤哥,我在从login跳转到index页面时报错:Authentication failed for token submission [org.apache.shiro.authc.UsernamePasswordToken

java.lang.NullPointerException: null 说是null,不明白这个null是哪个?
35 楼 xiangcunchouren 2017-07-18  
一般登录登录表单提交的时候,都是执行SecurityUtils.getSubject().login()才会出发shiro realm 的  doGetAuthenticationInfo()方法,可是群主的方法却没有调用次方法就触发了,能说一下其中的原理吗
34 楼 林祥纤 2017-07-07  
Kaiser8_Li 写道
登陆页面点登陆会下载favicon.icon ,页面也没跳转到index.html。这是什么原因,谁能告诉我下。。。代码是一摸一样的。用的浏览器是谷歌


还有人碰到嘛?
33 楼 Kaiser8_Li 2017-07-03  
登陆页面点登陆会下载favicon.icon ,页面也没跳转到index.html。这是什么原因,谁能告诉我下。。。代码是一摸一样的。用的浏览器是谷歌
32 楼 lzj_lives 2017-06-15  
纤哥。看了你的教程,从中学到了很多技巧包括一些配置。

现在遇到一个问题,使用了shiro安全验证后,在热部署的时候,就是编写了html也会是当前shiro登录失效,必须重新登录才可以。这个地方有什么好的解决方法吗?
31 楼 Mpolaris 2017-06-07  
引用

k88520 写道
林祥纤 写道
itlang01 写道
出现懒加载,no session异常


可以先看看这篇文章:79. could not initialize proxy - no Session 【从零开始学Spring Boot】

将你的userinfo类中的tostring方法里面的rolelist 删除
因为你在控制台打印userinfo信息的时候会调用查询方法 但是这个时候多对多的角色信息还没有开始查询  数据库的session还没开启  所有会报错  删除就好了



我一个一个文件匹配出来的。。。。终于找到问题所在
请问,我把里面的rolelist删除了,访问验证成功后,之后再把它加上,居然可以加载出数据了。这是为什么啊?郁闷!!!
30 楼 zqlovezxl 2017-04-07  
哥,关注我的SpringBoot实战项目,http://blog.csdn.net/zqy_zq_zxl
29 楼 zhuxunjun 2017-03-28  
看了好几遍这篇文章,还是解不开这个疑问。
我使用spring boot+shiro整合。
用户第一登陆时,会执行shiro的realm,我自己实现了个realm:public class MyShiroRealm extends AuthorizingRealm {...}

重载了方法doGetAuthenticationInfo做身份认证。
网上例子在这里都是通过String username = (String)authenticationToken.getPrincipal();
获取用户名,然后链接数据库查找用户,若找到了就return
new SimpleAuthenticationInfo(
                user.getPassword(),
                ByteSource.Util.bytes(user.getSalt()),
                getName()
        );
我第一次登陆时应该需要验证密码的呀,为什么这里只要匹配上了用户名就返回,它的校验工作到底在什么阶段完成的,又是怎么完成的呢?从哪里获取到用户名密码呢?
spring boot集成是在登陆的component那里直接通过requestHttp获取shiro的错误信息。不同的错误信息对应不同的错误。而没有subject.login(token);过程。
我猜是shiro内置的filter完成的这部分工作。但是具体的实现机制如何呢?它应该不知道哪个参数是用户名哪个是密码的,或者有约定。
28 楼 hxirui 2017-03-03  
zgjsyxwj 写道
你好,我按照你的例子配置了一遍,完全实现了你说的效果。现在有个问题,我在thymeleaf中用<a href="" shiro:hasPermission="100101102101">员工基础信息管理</a>这样的标签,却没有调用到权限验证方法doGetAuthorizationInfo。请问大哥知道如何解决吗?


在ShiroConfiguration最后添加下面内容
 @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true); // this SETTING
        return proxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager());
        return advisor;
    }
27 楼 lijunxian0114 2017-01-15  
你好,login.html页面登陆提交,是把请求提交到HomeController的login方法了吗
26 楼 zgjsyxwj 2017-01-05  
你好,我按照你的例子配置了一遍,完全实现了你说的效果。现在有个问题,我在thymeleaf中用<a href="" shiro:hasPermission="100101102101">员工基础信息管理</a>这样的标签,却没有调用到权限验证方法doGetAuthorizationInfo。请问大哥知道如何解决吗?
25 楼 林祥纤 2016-12-11  
yangjigang 写道
确实好文,正在学习,感谢分享。


Thank you very much !
24 楼 yangjigang 2016-12-11  
确实好文,正在学习,感谢分享。
23 楼 林祥纤 2016-11-22  
mgs2002 写道
mgs2002 写道
林祥纤 写道
mgs2002 写道
你好,我基本按照你的例子做了一篇,登录身份验证是没问题,但是权限验证方法doGetAuthorizationInfo没进入,请问大概是什么原因引起的,谢谢



1/开启shiro aop注解支持
2/controller加入注解

加了的,还是没进入。。

ShiroFilterFactoryBean里面加了DefaultAdvisorAutoProxyCreator开启spring aop代理就可以了


很好。
22 楼 mgs2002 2016-11-21  
mgs2002 写道
林祥纤 写道
mgs2002 写道
你好,我基本按照你的例子做了一篇,登录身份验证是没问题,但是权限验证方法doGetAuthorizationInfo没进入,请问大概是什么原因引起的,谢谢



1/开启shiro aop注解支持
2/controller加入注解

加了的,还是没进入。。

ShiroFilterFactoryBean里面加了DefaultAdvisorAutoProxyCreator开启spring aop代理就可以了
21 楼 mgs2002 2016-11-21  
林祥纤 写道
mgs2002 写道
你好,我基本按照你的例子做了一篇,登录身份验证是没问题,但是权限验证方法doGetAuthorizationInfo没进入,请问大概是什么原因引起的,谢谢



1/开启shiro aop注解支持
2/controller加入注解

加了的,还是没进入。。
20 楼 林祥纤 2016-11-12  
mgs2002 写道
你好,我基本按照你的例子做了一篇,登录身份验证是没问题,但是权限验证方法doGetAuthorizationInfo没进入,请问大概是什么原因引起的,谢谢



1/开启shiro aop注解支持
2/controller加入注解
19 楼 mgs2002 2016-11-11  
你好,我基本按照你的例子做了一篇,登录身份验证是没问题,但是权限验证方法doGetAuthorizationInfo没进入,请问大概是什么原因引起的,谢谢

相关推荐

Global site tag (gtag.js) - Google Analytics