在上一节我们编写了简单的一个小程序,但是我们会发现我们随便访问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
|