天天看點

shiro + extjs

源碼下載下傳 我的GITHUB

shiro 相比與spring security配置上簡單很多。

加載shiro config

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
           classpath:shiro-config.xml
        </param-value>
    </context-param>
           

添加shrio過濾器,這裡需要注意的是,過濾器的名字shiroFilter必須與配置檔案裡面的bean名字相同

<filter>
    	<filter-name>shiroFilter</filter-name>
    	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    	<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>
           

shiroFilter配置,這裡需要配置一個 formAuthenticationFilter,作用在後面會提到,主要是因為extjs的ajax請求。

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager"/>
		<!-- override these for application-specific URLs if you like:-->
		<property name="loginUrl" value="/login.html"/>
    	<property name="successUrl" value="/index.html"/>
    	<property name="unauthorizedUrl" value="/unauthorized.html"/>
    	<property name="filters">
        	<util:map>
            	<entry key="authc" value-ref="formAuthenticationFilter"/>
        	</util:map>
    	</property>
    	<property name="filterChainDefinitions">
    		<value>
    			/comp/** = anon
    			/pages/** = anon
    			/resources/** = anon
    			/view/** = anon
    			/** = authc
    		</value>
    	</property>
	</bean>
           

shiroFilter中引用了 securityManageer。如果參考shiro官網的同學請注意,官網documentation中的manager是org.apache.shiro.mgt.DefaultWebSecurityManager。在1.2.2中,這個類是沒有實作WebSecurityManager的,是以會跑出unimplement WebSecurityManager的異常。

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    	<!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. 如果有多個realm,需要用reamls參數,為實踐。-->
    	<property name="realm" ref="fastRealm"/>
    </bean>
           

引用的realm是可以重寫的。

<bean id="fastRealm" class="com.chinadreamer.contacts.filter.shiro.UserRealm"></bean>
           

重寫realm。

重寫的doGetAuthorizationInfo與doGetAuthenticationInfo在官網中已經很明确的說明了,這裡不再解釋。

我說下這裡會遇到的一個問題。shiro跟spring的filter并沒有優先級之分,而使用者驗證,一般都會涉及到datasource bean的引用,比方這裡的userservice。是以需要注意的是必須在spring bean加載之後,進入shiro,否則是無法加載的。

我的做法是在applicationContext.xml 結尾才引入shiro-config.xml,這樣就可以擷取到bean了。

引入方式比較正常

<import resource="shiro-config.xml"/>
           
public class UserRealm extends AuthorizingRealm{
	private final static Logger LOGGER = Logger.getLogger(UserRealm.class);
	
	
	@Autowired
	private UserService userService;
	
	public UserRealm(){
		super();
		LOGGER.info(" init user realm ");
		setAuthenticationTokenClass(UsernamePasswordToken.class);
	}
	
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		User user = ShiroUtils.getUser();

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        return authorizationInfo;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        String password = new String(upToken.getPassword());
        User user;
        try {
			user = this.userService.userLogin(username, password);
		} catch (Exception e) {
			throw new AuthenticationException(e.getMessage(),e);
		}
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password.toCharArray(), getName());
		return info;
	}

}
           
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
           

簡單的shiro到這就可以了。但是如果配合extjs,就需要控制ajax傳回,大家知道extj是根據response會寫的success 字段來判斷的。

<!--替換預設的form 驗證過濾器-->
    <bean id="formAuthenticationFilter" class="com.chinadreamer.contacts.filter.shiro.FastFormAuthenticationFilter">
    </bean>
           

提供一份源碼,根據各自的需求改動,這是其他大大提供的。

package com.chinadreamer.contacts.filter.shiro;

import java.io.IOException;
import java.io.PrintWriter;

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

import org.apache.log4j.Logger;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;

public class FastFormAuthenticationFilter extends FormAuthenticationFilter{
	private static final Logger LOGGER =  Logger.getLogger(FastFormAuthenticationFilter.class);
	
	/* 
     *  主要是針對登入成功的處理方法。對于請求頭是AJAX的之間傳回JSON字元串。 
     */  
	@Override
	protected boolean onLoginSuccess(AuthenticationToken token,
			Subject subject, ServletRequest request, ServletResponse response)
			throws Exception {
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;

		if (!"XMLHttpRequest".equalsIgnoreCase(httpServletRequest
				.getHeader("X-Requested-With"))) {// 不是ajax請求
			issueSuccessRedirect(request, response);
		} else {
			httpServletResponse.setCharacterEncoding("UTF-8");
			PrintWriter out = httpServletResponse.getWriter();
			out.println("{success:true,message:'登入成功'}");
			out.flush();
			out.close();
		}
		return false;
	}
	
	/** 
     * 主要是處理登入失敗的方法 
     */  
    @Override  
    protected boolean onLoginFailure(AuthenticationToken token,  
            AuthenticationException e, ServletRequest request,  
            ServletResponse response) {  
        if (!"XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request)  
                .getHeader("X-Requested-With"))) {// 不是ajax請求  
            setFailureAttribute(request, e);  
            return true;  
        }  
        try {  
            response.setCharacterEncoding("UTF-8");  
            PrintWriter out = response.getWriter();  
            String message = e.getClass().getSimpleName();  
            if ("IncorrectCredentialsException".equals(message)) {  
                out.println("{success:false,message:'密碼錯誤'}");  
            } else if ("UnknownAccountException".equals(message)) {  
                out.println("{success:false,message:'賬号不存在'}");  
            } else if ("LockedAccountException".equals(message)) {  
                out.println("{success:false,message:'賬号被鎖定'}");  
            } else {  
                out.println("{success:false,message:'未知錯誤'}");  
            }  
            out.flush();  
            out.close();  
        } catch (IOException e1) {  
            // TODO Auto-generated catch block  
            e1.printStackTrace();  
        }  
        return false;  
    }  
    
    /** 
     * 所有請求都會經過的方法。 
     */  
    @Override  
    protected boolean onAccessDenied(ServletRequest request,  
            ServletResponse response) throws Exception {  
  
        if (isLoginRequest(request, response)) {  
            if (isLoginSubmission(request, response)) {  
                if (LOGGER.isTraceEnabled()) {  
                	LOGGER.trace("Login submission detected.  Attempting to execute login.");  
                }  
                if ("XMLHttpRequest"  
                        .equalsIgnoreCase(((HttpServletRequest) request)  
                                .getHeader("X-Requested-With"))) {// 不是ajax請求  
//                    String vcode = request.getParameter("vcode");  
//                    HttpServletRequest httpservletrequest = (HttpServletRequest) request;  
//                    String vvcode = (String) httpservletrequest  
//                            .getSession()  
//                            .getAttribute(  
//                                    com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);  
//                    if (vvcode == null || "".equals(vvcode)  
//                            || !vvcode.equals(vcode)) {  
//                        response.setCharacterEncoding("UTF-8");  
//                        PrintWriter out = response.getWriter();  
//                        out.println("{success:false,message:'驗證碼錯誤'}");  
//                        out.flush();  
//                        out.close();  
//                        return false;  
//                    }  
                }  
                return executeLogin(request, response);  
            } else {  
                if (LOGGER.isTraceEnabled()) {  
                	LOGGER.trace("Login page view.");  
                }  
                // allow them to see the login page ;)  
                return true;  
            }  
        } else {  
            if (LOGGER.isTraceEnabled()) {  
            	LOGGER.trace("Attempting to access a path which requires authentication.  Forwarding to the "  
                        + "Authentication url [" + getLoginUrl() + "]");  
            }  
            if (!"XMLHttpRequest"  
                    .equalsIgnoreCase(((HttpServletRequest) request)  
                            .getHeader("X-Requested-With"))) {// 不是ajax請求  
                saveRequestAndRedirectToLogin(request, response);  
            } else {  
//                response.setCharacterEncoding("UTF-8");  
//                PrintWriter out = response.getWriter();  
//                out.println("{success:true,message:'login'}");  
//                out.flush();  
//                out.close();  
                return true;
            }  
            return false;  
        }  
    }  
}
           

繼續閱讀