SpringBoot集成shiro

本章节基于http://www.vxzsk.com/766.html讲解,建议首先看此章节然后在阅读学习下文

集成Shiro 进行用户授权

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


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

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

(b) 注入Shiro Factory和SecurityManager。

(c) 身份认证

(d) 权限控制

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

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

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

(b) 注入Shiro Factory和SecurityManager。

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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的规则和访问权限。
  *
 
  * @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执行,这时会调用Realm的getAuthenticationInfo(token)方法。

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

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

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

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

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

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

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


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

      在权限管理系统中,有这么几个角色很重要,这个要是不清楚的话,那么就很难理解,我们为什么这么编码了。第一是用户表:在用户表中保存了用户的基本信息,账号、密码、姓名,性别等;

第二是:权限表(资源+控制权限):这个表中主要是保存了用户的URL地址,权限信息;

第三就是角色表:在这个表重要保存了系统存在的角色;

第四就是关联表:用户-角色管理表(用户在系统中都有什么角色,比如admin,vip等),角色-权限关联表(每个角色都有什么权限可以进行操作)。依据这个理论,我们进行来进行编码,很明显的我们第一步就是要进行实体类的创建。在这里我们使用Mysql和JPA进行操作数据库。


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

1
2
3
4
5
6
7
8
9
10
11
<!-- 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新建一个即可):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
########################################################
###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、SysRole.java、SysPermission.java至于之前的关联表我们使用JPA进行自动生成。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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;
  
/**
  * 用户信息.
 
  * @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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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;
  
  
/**
  * 系统角色实体类;
 
  * @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 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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;
  
/**
  * 权限实体类;
  *
  * @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,对应的是数据库的五张表:

1表UserInfo、2表SysUserRole、3表SysRole、4表SysRolePermission、5表SysPermission

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package  com.kfit.core.repository;
  
import  org.springframework.data.repository.CrudRepository;
  
import  com.kfit.core.bean.UserInfo;
  
/**
  * UserInfo持久化类;
 
  * @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 :

1
2
3
4
5
6
7
8
9
10
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 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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;
  
/**
  *  身份校验核心类;
 
  * @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主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。

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

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

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

1
2
3
4
5
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中添加方法:

1
2
3
4
5
6
7
8
9
10
/**
      * 身份认证realm;
      * (这个需要自己写,账号密码校验;权限等)
      * @return
      */
     @Bean
     public  MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm =  new  MyShiroRealm();
        return  myShiroRealm;
     }

将myShiroRealm注入到securityManager中:

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 登录提交地址和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中加入方法:

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

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

1
2
3
4
5
6
7
8
9
10
11
/**
      * 身份认证realm;
      * (这个需要自己写,账号密码校验;权限等)
      * @return
      */
     @Bean
     public  MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm =  new  MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());;
        return  myShiroRealm;
     }

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


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

(d) 权限控制

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

在我们新建一个UserInfoController

com.kfit.core.controller.UserInfoController :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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加入如下方法进行开启即可:

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

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

1
2
3
4
5
6
7
8
9
/**
      * 用户添加;
      * @return
      */
     @RequestMapping ( "/userAdd" )
     @RequiresPermissions ( "userInfo:add" ) //权限管理;
     public  String userInfoAdd(){
        return  "userInfoAdd" ;
     }

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

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

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

1
2
3
4
5
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方法中加入:

1
2
3
4
5
6
7
8
9
/**
      * 用户删除;
      * @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 你会看到

1
2
3
4
5
6
7
权限配置-->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中加入缓存机制。

--------案例源码下载地址

http://dl.iteye.com/topics/download/d3ff2380-538d-35e3-b882-72cf044953d1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值