1. 程式人生 > >shiro + mybatis+ spring (只用shiro的密碼校驗和併發剔除)——不用許可權之類

shiro + mybatis+ spring (只用shiro的密碼校驗和併發剔除)——不用許可權之類

</pre>          shiro很強大,但往往專案不可能大改造,往往只需要部分功能,比如用到驗證碼,加密,還有就是同一個賬戶在兩個地方登入,剔除第一個登入者,本文只提供思路和部分程式碼,<p></p><p></p><p>自定義實現ream,</p><p></p><pre name="code" class="java">package com.shiro.shiro.realm;

import java.util.HashSet;
import java.util.Set;

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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import com.shiro.model.User;
import com.shiro.service.UserService;


public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;
    //獲取授權資訊
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
     <span style="font-size:18px;">   <span style="color:#ff0000;"><strong>Set<String>  set =new HashSet<String>();
        set.add("*:*:*");</strong></span>
        <span style="color:#ff0000;"><strong>authorizationInfo.setRoles(set);
        authorizationInfo.setStringPermissions(set);
        return authorizationInfo;</strong></span></span>
    }
   //獲取身份驗證資訊
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String username = (String)token.getPrincipal();

        User user = userService.findByUsername(username);

        if(user == null) {
            throw new UnknownAccountException();//沒找到帳號
        }

        if(Boolean.TRUE.equals(user.getLocked())) {
            throw new LockedAccountException(); //帳號鎖定
        }

        //交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,如果覺得人家的不好可以自定義實現
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user.getUsername(), //使用者名稱
                user.getPassword(), //密碼
                ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
                getName()  //realm name
        );
        return authenticationInfo;
    }

    @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();
    }

}

只要在<pre name="code" class="java">doGetAuthorizationInfo方法體裡授權所有資源所有角色就好了
</pre><pre name="code" class="java">
剔除功能
</pre><pre name="code" class="java"><pre name="code" class="java">package com.shiro.shiro.filter;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;

/**
 * 併發剔除
 */
public class KickoutSessionControlFilter extends AccessControlFilter {

    private String kickoutUrl; //踢出後到的地址
    private boolean kickoutAfter = false; //踢出之前登入的/之後登入的使用者 預設踢出之前登入的使用者
    private int maxSession = 1; //同一個帳號最大會話數 預設1

    private SessionManager sessionManager;
    private Cache<String, Deque<Serializable>> cache;

    public void setKickoutUrl(String kickoutUrl) {
        this.kickoutUrl = kickoutUrl;
    }

    public void setKickoutAfter(boolean kickoutAfter) {
        this.kickoutAfter = kickoutAfter;
    }

    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    public void setCacheManager(CacheManager cacheManager) {
        this.cache = cacheManager.getCache("shiro-kickout-session");
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        if(!subject.isAuthenticated() && !subject.isRemembered()) {//未授權且非remember 
            //如果沒有登入,直接進行之後的流程
            return true;
        }

        Session session = subject.getSession();
        String username = (String) subject.getPrincipal();
        Serializable sessionId = session.getId();

        //TODO 同步控制
        Deque<Serializable> deque = cache.get(username);
        if(deque == null) {
            deque = new LinkedList<Serializable>();
            cache.put(username, deque);
        }

        //如果佇列裡沒有此sessionId,且使用者沒有被踢出;放入佇列
        if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
            deque.push(sessionId);
        }

        //如果佇列裡的sessionId數超出最大會話數,開始踢人
        while(deque.size() > maxSession) {
            Serializable kickoutSessionId = null;
            if(kickoutAfter) { //如果踢出後者
                kickoutSessionId = deque.removeFirst();
            } else { //否則踢出前者
                kickoutSessionId = deque.removeLast();
            }
            try {
                Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
                if(kickoutSession != null) {
                    //設定會話的kickout屬性表示踢出了
                    kickoutSession.setAttribute("kickout", true);
                }
            } catch (Exception e) {//ignore exception
            }
        }

        //如果被踢出了,直接退出,重定向到踢出後的地址
        if (session.getAttribute("kickout") != null) {
            //會話被踢出了
            try {
                subject.logout();//這裡走的shiro的退出
            } catch (Exception e) { //ignore
            }
            saveRequest(request);
            WebUtils.issueRedirect(request, response, kickoutUrl);
            return false;
        }

        return true;
    }
}

此處用到了快取,當然可以自己實現,把session存到資料庫中然後判斷操作
</pre><pre name="code" class="java">再看下部分配置檔案
</pre><pre name="code" class="java">
<pre name="code" class="html"><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 快取管理器 -->
    <bean id="cacheManager" class="com.shiro.spring.cache.SpringCacheManagerWrapper">
        <property name="cacheManager" ref="springCacheManager"/>
    </bean>
    <!-- 憑證匹配器 -->
    <bean id="credentialsMatcher" class="com.shiro.shiro.credentials.CredentialsMatcher">
        <property name="hashAlgorithmName" value="md5"/>
        <property name="hashIterations" value="2"/>
        <property name="storedCredentialsHexEncoded" value="true"/>
    </bean>
    <!-- Realm實現 -->
    <bean id="userRealm" class="com.shiro.shiro.realm.UserRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
        <property name="cachingEnabled" value="false"/>
    </bean>
    <!-- 會話ID生成器 -->
    <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
    <!-- 會話Cookie模板 maxAge=-1表示瀏覽器關閉時失效此Cookie-->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="sid"/>
        <property name="httpOnly" value="true"/>
        <property name="maxAge" value="-1"/>
    </bean>
    <!-- 會話DAO -->
    <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
        <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
        <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
    </bean>
    <!-- 會話驗證排程器 -->
    <bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
        <property name="sessionValidationInterval" value="1800000"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>
    <!-- 會話管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="globalSessionTimeout" value="1800000"/>
        <property name="deleteInvalidSessions" value="true"/>
        <property name="sessionValidationSchedulerEnabled" value="true"/>
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
        <property name="sessionDAO" ref="sessionDAO"/>
        <property name="sessionIdCookieEnabled" value="true"/>
        <property name="sessionIdCookie" ref="sessionIdCookie"/>
    </bean>
    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm"/>
        <property name="sessionManager" ref="sessionManager"/>
        <property name="cacheManager" ref="cacheManager"/>
    </bean>
	
    <!-- 相當於呼叫SecurityUtils.setSecurityManager(securityManager) -->
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
        <property name="arguments" ref="securityManager"/>
    </bean>

    <!-- 基於Form表單的身份驗證過濾器 -->
<strong>    <bean id="authcFilter" class="com.shiro.shiro.filter.MyFormAuthenticationFilter">
        <property name="usernameParam" value="username"/>
        <property name="passwordParam" value="password"/>
        <property name="failureKeyAttribute" value="shiroLoginFailure"/>
    </bean>
    
     <bean id="validateFilter" class="com.shiro.shiro.filter.ValidateFilter">
        <property name="verificationAbled" value="true"/>
        <property name="verificationParam" value="verificationParam"/>
        <property name="failureKeyAttribute" value="shiroLoginFailure"/>
    </bean></strong>
       <!-- Shiro的Web過濾器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login"/>
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="<strong>authcFilter</strong>"/>
                <entry key="sysUser" value-ref="sysUserFilter"/>
                <entry key="validateFilter" value-ref="<strong>validateFilter</strong>"/>
                <entry key="kickout" value-ref="<strong>kickoutSessionControlFilter</strong>"/>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                 /login = <strong>validateFilter</strong>,authc
                 /logout = logout
                /authenticated = authc
                /** = <strong>kickout</strong>,user,sysUser
            </value>
        </property>
    </bean>
    
    <!-- currenuser  -->
    <bean id="sysUserFilter" class="com.shiro.shiro.filter.SysUserFilter"/>
    <!-- 併發踢出 
            kickoutAfter:是否踢出後來登入的,預設是false 
            kickoutUrl:被踢出後重定向到的地址
            maxSession:同一個使用者最大的會話數,預設1
     -->
    <bean id="kickoutSessionControlFilter" class="com.shiro.shiro.filter.KickoutSessionControlFilter">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="sessionManager" ref="sessionManager"/>
        <property name="kickoutAfter" value="false"/>
        <property name="maxSession" value="2"/>
        <property name="kickoutUrl" value="/login?kickout=1"/>
    </bean>
    <!-- Shiro生命週期處理器-->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    
</beans>


</pre><pre name="code" class="java">自定義的
</pre><pre name="code" class="java"><pre name="code" class="java">package com.shiro.shiro.filter;

import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * 自定義shiro FormAuthenticationFilter 表單過濾器
 * @author changliang
 *
 */
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
    /**
     * 授權是否失敗
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        if(request.getAttribute(getFailureKeyAttribute()) != null) {
            return true;
        }
        return super.onAccessDenied(request, response, mappedValue);
    }
}
</pre>密碼校驗<pre>
<pre name="code" class="java">package com.shiro.shiro.filter;

import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;

import com.shiro.service.UserService;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

/**
 * 驗證碼過濾器
 */
public class ValidateFilter extends AccessControlFilter {
	
	@SuppressWarnings("unused")
	@Autowired
	private UserService userService;

    private boolean verificationAbled = true;                //是否開啟驗證碼支援

    @SuppressWarnings("unused")
	private String verificationParam = "verificationParam";  //前臺提交的驗證碼引數名

    private String failureKeyAttribute = "shiroLoginFailure"; //驗證碼驗證失敗後儲存到的屬性名
    
   
    public void setVerificationAbled(boolean verificationAbled) {
		this.verificationAbled = verificationAbled;
	}

	public void setVerificationParam(String verificationParam) {
		this.verificationParam = verificationParam;
	}

	public void setFailureKeyAttribute(String failureKeyAttribute) {
        this.failureKeyAttribute = failureKeyAttribute;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        //1、設定驗證碼是否開啟屬性,頁面可以根據該屬性來決定是否顯示驗證碼
        request.setAttribute("verificationAbled", verificationAbled);

        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        //2、判斷驗證碼是否禁用 或不是表單提交(允許訪問)
        if (verificationAbled == false || !"post".equalsIgnoreCase(httpServletRequest.getMethod())) {
            return true;
        }
       <span style="color:#ff0000;"><strong> //3、此時是表單提交,驗證驗證碼是否正確
        //TODO 增加自己的驗證碼校驗
        //return  userService.verification(verificationParam);
        return true;</strong></span>
    }
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //如果驗證碼失敗了,儲存失敗key屬性
    	//在LoginController裡過濾用到
        request.setAttribute(failureKeyAttribute, "<span style="color:#ff0000;">verification.error</span>");
        return true;
    }
}



處理器
</pre><pre name="code" class="java"><pre name="code" class="java">package com.shiro.controller;

import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
/**
 * 常見錯誤:
 *  subject.login()
	 * DisabledAccountException(禁用的帳號)、
	 * LockedAccountException(鎖定的帳號)、
	 * UnknownAccountException(錯誤的帳號)、
	 * ExcessiveAttemptsException(登入失敗次數過多)、
	 * IncorrectCredentialsException (錯誤的憑證)、
	 * ExpiredCredentialsException(過期的憑證)
 * @author changliang
 *
 */
@Controller
public class LoginController {

    @RequestMapping(value = "/login"    )
    public String showLoginForm(HttpServletRequest req, Model model) {
        String exceptionClassName = (String)req.getAttribute("shiroLoginFailure");
        String error = null;
        if(UnknownAccountException.class.getName().equals(exceptionClassName)) {
            error = "使用者名稱/密碼錯誤";
        } else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
            error = "使用者名稱/密碼錯誤";//錯誤的憑證
        }else if("<span style="color:#ff0000;">verification.error</span>".equals(exceptionClassName)) {
            error = "驗證碼錯誤";
        } else if(exceptionClassName != null) {
            error = "其他錯誤:" + exceptionClassName;
        }
        model.addAttribute("error", error);
        return "login";
    }


}

參考 :http://jinnianshilongnian.iteye.com/blog/2049092

</pre><pre name="code" class="java">
</pre><pre name="code" class="java">
</pre><pre name="code" class="java">
</pre><pre name="code" class="java">
</pre>