(4). 集成Shiro 进行用户授权
在看此小节前,您可能需要先看:
http://412887952-qq-com.iteye.com/blog/2299732? ?
紧着上一小节,在上一节我们编写了简单的一个小程序,但是我们会发现我们随便访问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的规则和访问权限。
?*
?* @author Angel(QQ:412887952)
?* @version v.0.1
?*/
@Configuration
publicclass 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();
?????? returnsecurityManager;
??? }
???
???
???
}
这里说下: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的依赖:
?? ??? <!-- 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;
?
/**
?* 用户信息.
?* @author Angel(QQ:412887952)
?* @version v.0.1
?*/
@Entity
publicclass UserInfo implements Serializable{
??? privatestaticfinallongserialVersionUID = 1L;
??? @Id@GeneratedValue
??? privatelonguid;//用户id;
???
??? @Column(unique=true)
??? private String username;//账号.
???
??? private String name;//名称(昵称或者真实姓名,不同系统不同定义)
???
??? private String password; //密码;
??? private String salt;//加密密码的盐
???
??? privatebytestate;//用户状态,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() {
?????? returnroleList;
??? }
?
??? publicvoid setRoleList(List<SysRole> roleList) {
?????? this.roleList = roleList;
??? }
?
??? publiclong getUid() {
?????? returnuid;
??? }
?
??? publicvoid setUid(longuid) {
?????? this.uid = uid;
??? }
?
??? public String getUsername() {
?????? returnusername;
??? }
?
??? publicvoid setUsername(String username) {
?????? this.username = username;
??? }
?
??? public String getName() {
?????? returnname;
??? }
?
??? publicvoid setName(String name) {
?????? this.name = name;
??? }
?
??? public String getPassword() {
?????? returnpassword;
??? }
?
??? publicvoid setPassword(String password) {
?????? this.password = password;
??? }
?
??? public String getSalt() {
?????? returnsalt;
??? }
?
??? publicvoid setSalt(String salt) {
?????? this.salt = salt;
??? }
?
??? publicbyte getState() {
?????? returnstate;
??? }
?
??? publicvoid setState(bytestate) {
?????? this.state = state;
??? }
?
??? /**
??? ?* 密码盐.
??? ?* @return
??? ?*/
??? public String getCredentialsSalt(){
?????? returnthis.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
publicclass SysRole implements Serializable{
??? privatestaticfinallongserialVersionUID = 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() {
?????? returnuserInfos;
??? }
??? publicvoid setUserInfos(List<UserInfo> userInfos) {
?????? this.userInfos = userInfos;
??? }
??? public Long getId() {
?????? returnid;
??? }
??? publicvoid setId(Long id) {
?????? this.id = id;
??? }
??? public String getRole() {
?????? returnrole;
??? }
??? publicvoid setRole(String role) {
?????? this.role = role;
??? }
??? public String getDescription() {
?????? returndescription;
??? }
??? publicvoid setDescription(String description) {
?????? this.description = description;
??? }
??? public Boolean getAvailable() {
?????? returnavailable;
??? }
??? publicvoid setAvailable(Boolean available) {
?????? this.available = available;
??? }
??? public List<SysPermission> getPermissions() {
?????? returnpermissions;
??? }
??? publicvoid 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
publicclass SysPermission implements Serializable{
??? privatestaticfinallongserialVersionUID = 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;
???
??? publiclong getId() {
?????? returnid;
??? }
??? publicvoid setId(longid) {
?????? this.id = id;
??? }
??? public String getName() {
?????? returnname;
??? }
??? publicvoid setName(String name) {
?????? this.name = name;
??? }
??? public String getResourceType() {
?????? returnresourceType;
??? }
??? publicvoid setResourceType(String resourceType) {
?????? this.resourceType = resourceType;
??? }
??? public String getUrl() {
?????? returnurl;
??? }
??? publicvoid setUrl(String url) {
?????? this.url = url;
??? }
??? public String getPermission() {
?????? returnpermission;
??? }
??? publicvoid setPermission(String permission) {
?????? this.permission = permission;
??? }
??? public Long getParentId() {
?????? returnparentId;
??? }
??? publicvoid setParentId(Long parentId) {
?????? this.parentId = parentId;
??? }
??? public String getParentIds() {
?????? returnparentIds;
??? }
??? publicvoid setParentIds(String parentIds) {
?????? this.parentIds = parentIds;
??? }
??? public Boolean getAvailable() {
?????? returnavailable;
??? }
??? publicvoid setAvailable(Boolean available) {
?????? this.available = available;
??? }
??? public List<SysRole> getRoles() {
?????? returnroles;
??? }
??? 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持久化类;
?* @author Angel(QQ:412887952)
?* @version v.0.1
?*/
publicinterface 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;
?
publicinterface 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
publicclass UserInfoServiceImpl implements UserInfoService{
???
??? @Resource
??? private UserInfoRepository userInfoRepository;
???
??? @Override
??? public UserInfo findByUsername(String username) {
?????? System.out.println("UserInfoServiceImpl.findByUsername()");
?????? returnuserInfoRepository.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
?*/
publicclass 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){
?????????? returnnull;
?????? }
??????
?????? /*
?????? ?* 获取权限信息:这里没有进行实现,
?????? ?* 请自行根据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
//????? );
??????
?????? returnauthenticationInfo;
??? }
???
???
???
??? /**
??? ?* 此方法调用? 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()));
??????
?????? returnauthorizationInfo;
??? }
???
???
??? /**
??? ?* 将权限对象中的权限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();
?????? returnmyShiroRealm;
??? }
将myShiroRealm注入到securityManager中:
@Bean
??? public SecurityManager securityManager(){
?????? DefaultWebSecurityManager securityManager =? new DefaultWebSecurityManager();
?????? //设置realm.
?????? securityManager.setRealm(myShiroRealm());
?????? returnsecurityManager;
??? }
?
到这里的话身份认证权限控制基本是完成了,最后我们在编写一个登录的时候,登录的处理:
在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(""));
??????
?????? returnhashedCredentialsMatcher;
??? }
在myShiroRealm()方法中注入凭证匹配器:
/**
??? ?* 身份认证realm;
??? ?* (这个需要自己写,账号密码校验;权限等)
??? ?* @return
??? ?*/
??? @Bean
??? public MyShiroRealm myShiroRealm(){
?????? MyShiroRealm myShiroRealm = new MyShiroRealm();
?????? myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());;
?????? returnmyShiroRealm;
??? }
?
这时候在访问/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")
publicclass 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);
?????? returnauthorizationAttributeSourceAdvisor;
??? }
?
第二就是在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 系列博客】
?
0)前言【从零开始学Spring Boot】 :
http://412887952-qq-com.iteye.com/blog/2291496
?
(1)spring boot起步之Hello World【从零开始学Spring Boot】:
http://412887952-qq-com.iteye.com/blog/2291500
(2)Spring Boot返回json数据【从零开始学Spring Boot】
http://412887952-qq-com.iteye.com/blog/2291508
(15)Spring Boot使用Druid和监控配置【从零开始学Spring Boot】
http://412887952-qq-com.iteye.com/blog/2292362
16)Spring Boot使用Druid(编程注入)【从零开始学Spring Boot】
http://412887952-qq-com.iteye.com/blogs/2292376
(17)Spring Boot普通类调用bean【从零开始学Spring Boot】:
http://412887952-qq-com.iteye.com/blog/2292388
......
(35)Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】
http://412887952-qq-com.iteye.com/blog/2294942
?
?
更多查看博客:http://412887952-qq-com.iteye.com/
?
?
(39.2). Spring Boot Shiro权限管理【从零开始学Spring Boot】
原文:http://412887952-qq-com.iteye.com/blog/2299777