从本篇起,开始做一些日常工作小结。由于本人依旧属于刚入门级别,所以做出的一些总结难免会有一些不严谨,甚至是错误;但本人并不想误人子弟,只是想记录的轨迹,仅此而已。
使用shiro,先要导入相关jar包
使用IDEA的,且创建的是maven工程的,可直接在pom.xml引入
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
shiro和spring的整合配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--suppress ALL, SpringModelInspection -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 配置SecurityManager、自定义Realm、定义加密算法、自定义二级缓存 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!-- 配置自定义第三方缓存EhCache -->
<bean id="shiroCache" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
</bean>
<!-- 自定义的realm -->
<bean id="myRealm" class="com.jz.xd.service.shiro.MyRealm">
<!-- 添加自定义检验规则 -->
<property name="credentialsMatcher" ref="passwordMatcher"/>
</bean>
<!-- remenberMe配置 -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<!-- 默认记住7天(单位:秒) -->
<property name="maxAge" value="604800"/>
</bean>
<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
<property name="cookie" ref="rememberMeCookie"/>
</bean>
<!-- 自定义加密算法 -->
<bean id="passwordMatcher" class="com.jz.xd.service.shiro.CustomCredentialsMatcher"/>
<!-- 过滤URL,filter。这个id名称必须和web.xml中声明的filter一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--登录失败之后指向的url-->
<property name="loginUrl" value="/index/toLogin"/>
<!--无权限指向的页面-->
<property name="unauthorizedUrl" value="/index/toLogin"/>
<property name="filterChainDefinitions">
<!-- 哪些jsp,action等其他资源可以放行,哪些jsp,action不能放行。配置时按先后顺序进行url过滤 -->
<value>
<!--权限规则-->
<!--anon:例子/admins/**=anon 没有参数,表示可以匿名使用。-->
<!--authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数-->
<!--roles:例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。-->
<!--perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。-->
<!--rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。-->
<!--port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。-->
<!--authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证-->
<!--ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https-->
<!--user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查-->
<!--静态资源和登录页不拦截-->
/index.jsp =anon
/html/**=anon
/resources/** =anon
<!--对每个Controller做拦截-->
/index/**=anon
/login/toInformation=user
<!--...-->
/login/toShopCar=authc
<!--...-->
/root/upload/toUploadVideo=authc,perms[/root/upload/toUploadVideo]
<!--...-->
</value>
</property>
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 生成代理,通过代理进行控制 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true"/>
</bean>
<!-- 安全管理器 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
后一个/root/upload/toUploadVideo是权限名称,可以自己重命名,这里直接用拦截的路径。
其中:
package com.jz.xd.service.shiro;
import com.jz.xd.model.SPermission;
import com.jz.xd.model.Srole;
import com.jz.xd.model.User;
import com.jz.xd.service.IndexService;
import com.jz.xd.service.RootService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.authc.*;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Created by HARU on 2017/3/17.
*/
public class MyRealm extends AuthorizingRealm {
@Autowired
private IndexService indexService;
@Autowired
private RootService rootService;
private static Logger logger = LogManager.getLogger(MyRealm.class);
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("权限");
User user = (User) principalCollection.getPrimaryPrincipal();
if (user != null) {
User userEntity = indexService.getUser(user.getUsername());
List<String> roles = new ArrayList<String>();
List<String> permissions = new ArrayList<String>();
List<Srole> role = rootService.getRole(userEntity);
for (Srole srole : role) {
roles.add(srole.getRolename());
List<SPermission> perms = indexService.getPerms(srole);
for (SPermission sPermission : perms) {
if (!StringUtils.isEmpty(sPermission.getUrl())) {
permissions.add(sPermission.getUrl());
}
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//添加角色信息
simpleAuthorizationInfo.addRoles(roles);
//添加权限信息
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
} else {
return null;
}
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.debug("验证登录");
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
//获取前台传入的username
String username = usernamePasswordToken.getUsername();
if (!StringUtils.isEmpty(username)) {
//如果用名不为空
User userEntity = indexService.getUser(username);
if (userEntity == null) {
userEntity = indexService.getUserByPhone(username).get(0);
}
if (userEntity != null) {
return new SimpleAuthenticationInfo(userEntity, userEntity.getPassword(), this.getName());
}
}
return null;
}
}
package com.jz.xd.service.shiro;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
/**
* Created by HARU on 2017/3/17.
*/
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken usertoken = (UsernamePasswordToken) token;
//注意token.getPassword()拿到的是一个char[],不能直接用toString(),它底层实现不是我们想的直接字符串,只能强转
Object tokenCredentials = Encrypt.md5hash(String.valueOf(usertoken.getPassword()), Encrypt.SALT);
Object accountCredentials = getCredentials(info);
//将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false
return equals(tokenCredentials, accountCredentials);
}
}
package com.jz.xd.service.shiro;
import org.apache.shiro.crypto.hash.Md5Hash;
/**
* Created by HARU on 2017/3/17.
*/
public class Encrypt {
public static final String SALT = "HARU";
/*
* 两个参数:1)需要加密密码,2)盐(混淆、动态值) 用户名 3)hash次数
*
* 例如:
* password=123456+“123”、“abc”
*/
public static String md5hash(String password, String salt) {
return new Md5Hash(password, salt, 2).toString();
}
public static void main(String[] args) {
System.out.println(Encrypt.md5hash("root", SALT));
}
}
同时,web.xml中要添加:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-service.xml,classpath:spring-shiro.xml</param-value>
</context-param>
<!--shiro 权限-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
jsp文件上要想使用shiro标签,先引入<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:guest></shiro:guest>标签代表未登录时游客的访问,可以看到的内容。
<shiro:user> </shiro:user>标签代表登录以后可以看到的内容,<shiro:principal property="username"/>可以在登录以后显示用户名。
之后在进行登录的逻辑时:
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
if (rememberMe == null) {
rememberMe = false;
}
usernamePasswordToken.setRememberMe(rememberMe);
subject.login(usernamePasswordToken);
Object object = subject.getPrincipal();
//把信息放在Shiro的session中
//shiro默认的session就是httpSession
subject.getSession().setAttribute("user", users.get(0));
注销:SecurityUtils.getSubject().logout();
补充:关于权限角色。
可以先创建几张表:
CREATE TABLE `role_perm` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色权限ID',
`roleId` int(11) DEFAULT NULL COMMENT '角色ID',
`permId` int(11) DEFAULT NULL COMMENT '权限ID',
PRIMARY KEY (`id`)
)
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`roleName` varchar(255) DEFAULT NULL COMMENT '角色名',
`roleDescription` varchar(255) DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`id`)
)
CREATE TABLE `sys_permission` (
`prid` int(11) NOT NULL AUTO_INCREMENT COMMENT '权限ID',
`modelName` varchar(255) DEFAULT NULL COMMENT '拦截名称',
`url` varchar(255) DEFAULT NULL COMMENT '拦截的路径',
`parentId` int(11) DEFAULT NULL COMMENT '父Id',
PRIMARY KEY (`prid`)
)
CREATE TABLE `user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户权限ID',
`uid` int(11) DEFAULT NULL COMMENT '用户iD',
`roleId` int(11) DEFAULT NULL COMMENT '角色ID',
PRIMARY KEY (`id`)
)
shiro角色权限的一个应用是在后台管理系统中,侧边栏的操作列表:
可以将一级列表设为父权限,同时也是角色名,这样表的数据如下:
权限表:
角色表(图1):
并且创建两个实体:
public class Pmenu {
private String pid;
private String pMenuName;
private String url;
private List<Cmenu> cmenus;
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public String getpMenuName() {
return pMenuName;
}
public void setpMenuName(String pMenuName) {
this.pMenuName = pMenuName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public List<Cmenu> getCmenus() {
return cmenus;
}
public void setCmenus(List<Cmenu> cmenus) {
this.cmenus = cmenus;
}
}
package com.jz.xd.model;
/**
* Created by HARU on 2017/6/16.
*/
public class Cmenu {
private String cid;
private String cMenuName;
private String url;
public String getCid() {
return cid;
}
public void setCid(String cid) {
this.cid = cid;
}
public String getcMenuName() {
return cMenuName;
}
public void setcMenuName(String cMenuName) {
this.cMenuName = cMenuName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
这样在加入后台时,获取权限角色列表,用mybatis:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.jz.xd.mapper.ext.PermissionMapperExt">
<!--id用于合并结果相关的记录-->
<resultMap id="mune" type="com.jz.xd.model.Pmenu">
<id property="pid" column="pid"/>
<result property="pMenuName" column="pname"/>
<result property="url" column="purl"/>
<collection property="cmenus" ofType="com.jz.xd.model.Cmenu">
<id property="cid" column="cid"/>
<result property="cMenuName" column="cname"/>
<result property="url" column="curl"/>
</collection>
</resultMap>
<select id="getMenu" resultMap="mune">
SELECT
sp_a.prid cid,
sp_a.modelName cname,
sp_a.url curl,
sp_b.modelName pname,
sp_b.prid pid,
sp_b.url purl
FROM
roleperm srp
LEFT JOIN s_permission sp_a ON srp.permId = sp_a.prid
LEFT JOIN s_permission sp_b ON sp_b.prid = sp_a.parentId
WHERE
<if test="rIds!=null">
srp.roleId IN
(
<foreach collection="rIds" index="index" item="rId" separator=",">
#{rId}
</foreach>
)
</if>
/*使用order by field(roleId,5,1)会将IN中的条件按这样的顺序查询,否则默认是按roleId递增查询*/
</select>
</mapper>
mysql查询出的是:
注意,使用这样就能体现resultMap的好处,设置了ID就可以保证角色的唯一,即使在添加角色时,就如图1 ,添加了id为1,5的角色,即使名字都是上传,但是,进过连接得到的权限名都会映射到同一个父权限名“上传”,且pid是相同的,所有也只会在列表上显示一个“上传”;且子权限也会同上一样,结果合并且只显示一个。