spring boot 集成shiro(用户授权和权限控制)
来源:互联网 发布:js实现图片放大镜效果 编辑:程序博客网 时间:2024/06/06 00:43
在上一节我们编写了简单的一个小程序,但是我们会发现我们随便访问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 Factory和SecurityManager。
在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的规则和访问权限。
*
*
* @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 NameClassanonorg.apache.shiro.web.filter.authc.AnonymousFilterauthcorg.apache.shiro.web.filter.authc.FormAuthenticationFilterauthcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilterpermsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilterportorg.apache.shiro.web.filter.authz.PortFilterrestorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilterrolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFiltersslorg.apache.shiro.web.filter.authz.SslFilteruserorg.apache.shiro.web.filter.authc.UserFilteranon:所有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的依赖:
<!-- Spirng data JPA依赖; -->
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-data-jpa</
artifactId
>
</
dependency
>
<!-- mysql驱动; -->
<
dependency
>
<
groupId
>mysql</
groupId
>
<
artifactId
>mysql-connector-java</
artifactId
>
</
dependency
>
配置src/main/resouces/application.properties配置数据库和jpa(application.properties新建一个即可):
########################################################
###datasource
########################################################
spring.datasource.url = jdbc:mysql:
//localhost:3306/test
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.max-active=
20
spring.datasource.max-idle=
8
spring.datasource.min-idle=
8
spring.datasource.initial-size=
10
########################################################
### Java Persistence Api
########################################################
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log
for
each sql query
spring.jpa.show-sql =
true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
#[org.hibernate.cfg.ImprovedNamingStrategy | org.hibernate.cfg.DefaultNamingStrategy]
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
准备工作准备好之后,那么就可以编写实体类了:
UserInfo.java、SysRole.java、SysPermission.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;
/**
* 用户信息.
*
* @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;
/**
* 系统角色实体类;
*
* @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;
/**
* 权限实体类;
*
* @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
这时候运行程序,就会自动建表,然后我们添加一些数据:
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持久化类;
*
* @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;
/**
* 身份校验核心类;
*
* @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中加入缓存机制。
- spring boot 集成shiro(用户授权和权限控制)
- spring boot 1.5.4 集成shiro+cas,实现单点登录和权限控制
- spring boot(四)shiro权限集成
- Spring Boot +Shiro 用户角色权限设计
- Shiro+Spring+Struts2集成演示权限控制
- spring mvc集成shiro权限控制
- spring-boot集成shiro
- spring boot集成shiro
- [spring-boot] 集成shiro
- spring boot 集成shiro
- spring boot 集成 shiro
- Spring Boot 集成Shiro和CAS
- Spring Boot 集成Shiro和CAS
- Spring Boot 集成Shiro和CAS
- Spring Boot 集成Shiro和CAS
- Spring Boot 集成Shiro和CAS
- Spring Boot 集成Shiro和CAS
- Spring Boot 集成Shiro和CAS
- 产学研的含义
- Hdu 2062 Subset sequence
- 静态pdf表单与交互式pdf表单的介绍
- codeforces Div#432 B Arpa and an exam about geometry(技巧)
- centos6.8安装Jdk1.8
- spring boot 集成shiro(用户授权和权限控制)
- windows命令方式操作防火墙
- 树莓派+FFmpeg——推送摄像头数据到RTMP服务器
- 【Leetcode】【python】Reverse Nodes in k-Group
- win10端口被占用如何查看
- mysql源码编译通过后,安装和启动mysql服务
- 笔记本热点设置
- 实现炮塔打兵
- 《嵌入式开发探秘》之第二章 开发环境搭建(3)