Shiro的Web项目配置
一 shiro的学习
二 shiro的java客户端配置
三 关于权限的一些问题
一 shiro的学习
官网和张开涛博客
二 shiro的java客户端配置
1.在web.xml中配置shiro的过滤器
<!-- shiro 安全过滤器 -->
<!-- The filter-name matches name of a 'shiroFilter' bean inside -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
同时在web.xml读取shiro的配置文件
2.在spring-shiro-web.xml中配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 为了获取adminPath的值 -->
<context:property-placeholder ignore-unresolvable="true" location="classpath*:/system.properties"/>
<!-- 配置安全管理中心,Shiro的主要业务层对象基于web的应用程序 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
</bean>
<!-- 自定义的过滤器,用来验证登陆 -->
<bean id="formAuthenticationCaptchaFilter" class="com.huaxia.shiro.FormAuthenticationCaptchaFilter">
<property name="usernameParam" value="username"/>
<property name="passwordParam" value="password"/>
<property name="captchaParam" value="captcha"/>
<property name="loginUrl" value="${adminPath}/login"/>
</bean>
<!-- 自定义的过滤器 -->
<bean id="userFilter" class="com.huaxia.shiro.HuaXiaUserFilter">
</bean>
<!-- Shiro的Web过滤器,在web.xml中配置的shiroFilter即指向此 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 指定一个安全管理中心,用来验证用户能否登陆和相关权限 -->
<property name="securityManager" ref="securityManager"/>
<!-- 所有地址被重定向到该地址 -->
<property name="loginUrl" value="${adminPath}/login"/>
<!-- 用户登录成功后的页面地址 -->
<property name="successUrl" value="${adminPath}"/>
<!-- 过滤器链,在shiroFilter之前即开始执行 -->
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationCaptchaFilter"/>
<entry key="user" value-ref="userFilter"/>
</util:map>
</property>
<!-- 配置地址对应的过滤器 -->
<property name="filterChainDefinitions">
<value>
${adminPath}/login = authc
/** = user
</value>
</property>
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 下面两个bean是shiro官网推荐的配置 -->
<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>
其中UserRealm的实现如下:
package com.huaxia.shiro;
import javax.annotation.PostConstruct;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.code.kaptcha.Constants;
import com.huaxia.Constant;
import com.huaxia.common.utils.security.Digests;
import com.huaxia.common.utils.security.Encodes;
import com.huaxia.user.entity.User;
import com.huaxia.user.service.UserService;
@Service
public class UserRealm extends AuthorizingRealm {
private Logger logger = LoggerFactory.getLogger(UserRealm.class);
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findRoles(username));
authorizationInfo.setStringPermissions(userService.findPermissions(username));
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordCaptchaToken captchaToken = (UsernamePasswordCaptchaToken) token;
String username = String.valueOf(token.getPrincipal());
User user = userService.findByUsername(username,Constant.USER_DELFLAG);
if(null != user && doCaptchValidate(captchaToken)) {
if (Boolean.TRUE.equals(user.getLocked())) {
throw new LockedAccountException(); //帐号锁定
}
if(Constant.LOGIN_STATUS_N.equals(user.getLoginStatus())){
throw new DisabledAccountException();
}
byte[] salt = Encodes.decodeHex(user.getSalt());
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,可以自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUserName(),
user.getPassword(), //密码
ByteSource.Util.bytes(salt),
getName() //realm name
);
//SecurityUtils.getSubject().getSession().setAttribute("user", user);
SecurityUtils.getSubject().getSession().setAttribute("userId", user.getUserId());
userService.updateByLogin(user);
return authenticationInfo;
}else{
throw new UnknownAccountException();
}
}
protected boolean doCaptchValidate(UsernamePasswordCaptchaToken token){
String captcha = (String) SecurityUtils.getSubject().getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
if(captcha != null && !captcha.equalsIgnoreCase(token.getCaptcha())){
throw new CaptchaException("Code error");
}
return true;
}
@PostConstruct
public void initCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(Digests.SHA1);
matcher.setHashIterations(Constant.HASH_INTERATIONS);
setCredentialsMatcher(matcher);
}
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
}
其中formAuthenticationCaptchaFilter的实现如下:
package com.huaxia.shiro;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FormAuthenticationCaptchaFilter extends FormAuthenticationFilter {
private Logger logger = LoggerFactory.getLogger(FormAuthenticationCaptchaFilter.class);
public static final String DEFAULT_CAPTCHA_PARAM = "captcha";
private String captchaParam = DEFAULT_CAPTCHA_PARAM;
public String getCaptchaParam() {
return captchaParam;
}
public void setCaptchaParam(String captchaParam){
this.captchaParam = captchaParam;
}
protected String getCaptcha(ServletRequest request) {
return WebUtils.getCleanParam(request, getCaptchaParam());
}
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
String username = getUsername(request) == null ? "" : getUsername(request);
String password = getPassword(request) == null ? "" : getPassword(request);
String captcha = getCaptcha(request) == null ? "" : getCaptcha(request);
boolean rememberMe = isRememberMe(request);
return new UsernamePasswordCaptchaToken(username,password.toCharArray(), rememberMe, captcha);
}
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
ServletRequest request, ServletResponse response) throws Exception {
// issueSuccessRedirect(request, response);
// we handled the success redirect directly, prevent the chain from continuing:
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
if (!"XMLHttpRequest".equalsIgnoreCase(httpServletRequest.getHeader("X-Requested-With"))
|| request.getParameter("ajax") == null) {// 不是ajax请求
httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + this.getSuccessUrl());
} else {
httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + this.getSuccessUrl());
}
return false;
}
}
其中HuaXiaUserFilter的实现如下:
package com.huaxia.shiro;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.apache.shiro.web.filter.session.NoSessionCreationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
public class HuaXiaUserFilter extends UserFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
/*if(!"XMLHttpRequest".equalsIgnoreCase(WebUtils.toHttp(response).getHeader("X-Requested-With"))
|| request.getParameter("ajax") == null ){
this.saveRequestAndRedirectToLogin(request, response);
}else{*/
HttpServletResponse res = WebUtils.toHttp(response);
res.setHeader("sessionstatus","timeout");
//res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
/* }*/
this.saveRequestAndRedirectToLogin(request, response);
return false;
}
}
3.另外在Mvc的配置文件spring-mvc.xml中加入如下拦截器内容:
<aop:config proxy-target-class="true"></aop:config>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
三 关于权限的一些问题
1.shiro如何实现验证码?
关于shiro的验证码,使用的是google的kaptcha , 在web.xml中配置sevlet如下:
<servlet>
<servlet-name>ImageServlet</servlet-name>
<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ImageServlet</servlet-name>
<url-pattern>/ImageServlet</url-pattern>
</servlet-mapping>
由于通常密码保存使用的是md5加密,同样的密码在md5后会产生相同的加密后字符串,如果有数据库的查看权限,那么看到相同的加密后字符串,很容易猜到是相同的密码。同时根据md5对照表(或者一些网站),能够找到密码。 如果密码在加上随机盐后再进行md5,那么同样的密码在md5后的字符串是不同的,就能够避免上面两个问题。总的来说增加了破解的难度。
3.通常情况下用户-角色-权限(资源)之间的关系
用户与角色多对多:一个用户可以拥有多个角色,一个角色可以被多个用户具有
角色与权限(资源,主要指菜单,按钮等)多对多:一个角色可以拥有多个权限,一个权限可以被多个角色拥有
3.1 在首页,通常根据用户查找角色,然后根据角色列出相关的菜单栏和相关按钮
3.2 在系统配置中:
用户配置:可以增删用户,也可以配置用户的多个角色
角色配置:可以配置一个角色的多个权限
权限配置:可以增删相关的资源(菜单或者按钮)
4.shiro授权的几种方式:
4.1 在代码体中:
if (currentUser.hasRole("admin"))
4.2 在方法上:
@RequiresPermissions(“account:create”)
public void openAccount( Account acct )
4.3 在jsp页面上:
<shiro:hasPermission name=“users:manage”>
<a href=“manageUsers.jsp”> </a>
</shiro:hasPermission>
Shiro的官方文档整理的感觉差强人意,非常不明朗,需要结合张开涛的博客来看。有很多的地方需要学习,后续更新。