`

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
分享到:
评论
18 楼 k88520 2016-11-02  
k88520 写道
林祥纤 写道
itlang01 写道
出现懒加载,no session异常


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

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



我一个一个文件匹配出来的。。。。终于找到问题所在
17 楼 k88520 2016-11-02  
林祥纤 写道
itlang01 写道
出现懒加载,no session异常


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

将你的userinfo类中的tostring方法里面的rolelist 删除
因为你在控制台打印userinfo信息的时候会调用查询方法 但是这个时候多对多的角色信息还没有开始查询  数据库的session还没开启  所有会报错  删除就好了
16 楼 林祥纤 2016-10-04  
itlang01 写道
出现懒加载,no session异常


可以先看看这篇文章:79. could not initialize proxy - no Session 【从零开始学Spring Boot】
15 楼 itlang01 2016-10-02  
出现懒加载,no session异常
14 楼 林祥纤 2016-09-26  
z736732419 写道
z736732419 写道

纤哥,之前在配置shiro的时候不是指定了shiroFilterFactoryBean.setUnauthorizedUrl("/403");未授权页面跳转的地址了么,为什么它没有跳转到指定的403页面而只是在后台报未授权的异常呢,求解


纤哥,我通过直接在shiroFilterFactoryBean.put("/user/list", "authc,roles[admin]");添加了对user/list页面的访问权限控制,这样就成功跳转到403,说明它确实走了Unauthorized,但是为什么注解没搞定,百思不得姐呀



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

你这目前还真看不出来,信息量太少。
13 楼 z736732419 2016-09-25  
z736732419 写道

纤哥,之前在配置shiro的时候不是指定了shiroFilterFactoryBean.setUnauthorizedUrl("/403");未授权页面跳转的地址了么,为什么它没有跳转到指定的403页面而只是在后台报未授权的异常呢,求解


纤哥,我通过直接在shiroFilterFactoryBean.put("/user/list", "authc,roles[admin]");添加了对user/list页面的访问权限控制,这样就成功跳转到403,说明它确实走了Unauthorized,但是为什么注解没搞定,百思不得姐呀
12 楼 z736732419 2016-09-25  

纤哥,之前在配置shiro的时候不是指定了shiroFilterFactoryBean.setUnauthorizedUrl("/403");未授权页面跳转的地址了么,为什么它没有跳转到指定的403页面而只是在后台报未授权的异常呢,求解
11 楼 林祥纤 2016-09-21  
qq1488888 写道
你好,想问下doGetAuthenticationInfo(AuthenticationToken token)里面的token.getPrincipal()是怎么拿到username的,是默认在login提交的表单的username就自动给到这个属性?


底层通过获取到request对象,然后进行赋值。
10 楼 qq1488888 2016-09-20  
你好,想问下doGetAuthenticationInfo(AuthenticationToken token)里面的token.getPrincipal()是怎么拿到username的,是默认在login提交的表单的username就自动给到这个属性?
9 楼 林祥纤 2016-08-02  
zhongxunking 写道
你好,一直在关注你springboot系列博客,写的很棒。不过本章集成shiro却没看懂,主要原因是很乱,内容很多,可能是我接受能力有限。我内心比较期望的讲解方式是:1、先讲解整个流程(就像你讲每个知识点前把要做的每个点列出来,很棒);2、讲解不用shiro时如何实现权限控制(比如在controller里自己实现控制),同时这种实现方式有什么缺点;3、讲解shiro怎么帮我们实现权限控制(只需讲解最简单的集成方式),这种会有什么优点,给我们代码会带来哪些优雅,然后可根据实际情况层层进行深入讲解。当然这样讲解的话肯定会消耗你很多时间,你酌情考虑。真的很感谢你讲的这一系列springboot教程,却大多数都很通熟易懂,很实用。我学到了很多,我会一直关注。


感谢您对我的认可,对于您的建议我会慎重考虑下的;对于有些知识点吧,这里也不可能面面俱到,您可以通过百度查找相关的知识点,进行自我学习下。
8 楼 zhongxunking 2016-08-02  
你好,一直在关注你springboot系列博客,写的很棒。不过本章集成shiro却没看懂,主要原因是很乱,内容很多,可能是我接受能力有限。我内心比较期望的讲解方式是:1、先讲解整个流程(就像你讲每个知识点前把要做的每个点列出来,很棒);2、讲解不用shiro时如何实现权限控制(比如在controller里自己实现控制),同时这种实现方式有什么缺点;3、讲解shiro怎么帮我们实现权限控制(只需讲解最简单的集成方式),这种会有什么优点,给我们代码会带来哪些优雅,然后可根据实际情况层层进行深入讲解。当然这样讲解的话肯定会消耗你很多时间,你酌情考虑。真的很感谢你讲的这一系列springboot教程,却大多数都很通熟易懂,很实用。我学到了很多,我会一直关注。
7 楼 林祥纤 2016-07-15  
valeurliu 写道
public interface UserInfoRepository extends CrudRepository<UserInfo,Long>{
/**通过username查找用户信息;*/
public UserInfo findByUsername(String username);
}
这个findByUsername是怎么从数据库里查找出的信息啊?


建议先研究下spring jpa知识;
6 楼 林祥纤 2016-07-15  
valeurliu 写道
直接插入数据库里面的加密的密码和salt是怎么产生的呢?


看shiro 加密算法;
5 楼 valeurliu 2016-07-14  
public interface UserInfoRepository extends CrudRepository<UserInfo,Long>{
/**通过username查找用户信息;*/
public UserInfo findByUsername(String username);
}
这个findByUsername是怎么从数据库里查找出的信息啊?
4 楼 valeurliu 2016-07-14  
直接插入数据库里面的加密的密码和salt是怎么产生的呢?
3 楼 林祥纤 2016-05-29  
hacker10086 写道
你这里面导入的数据和我自己生成的表不对应 自己生成的表名有下滑杠连接符,你这没有,研究了一会才找出来 (没找到插图的地方)




spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy

----------------------------------
有两种常用的方式 :org.hibernate.cfg.DefaultNamingStrategy ;

另外一种就是:org.hibernate.cfg.ImprovedNamingStrategy,这里根据自己的需要进行选择一种方式。
2 楼 林祥纤 2016-05-29  
hacker10086 写道
你这里面导入的数据和我自己生成的表不对应 自己生成的表名有下滑杠连接符,你这没有,研究了一会才找出来 (没找到插图的地方)


这个主要是由这个配置决定的:

spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy
1 楼 hacker10086 2016-05-29  
你这里面导入的数据和我自己生成的表不对应 自己生成的表名有下滑杠连接符,你这没有,研究了一会才找出来 (没找到插图的地方)

相关推荐

Global site tag (gtag.js) - Google Analytics