前後端分離架構使用shiro框架進行登錄的兩種實現

方法一:重寫FormAuthenticationFilter

原理:

假設在shiro.xml中配置了 /** = authc

而默認authc對應org.apache.shiro.web.filter.authc.FormAuthenticationFilter過濾器

則表示所有路徑都被此過濾器攔截

 

當未登錄請求被攔截,會調用FormAuthenticationFilter.onAccessDeny():
如果請求的是loginUrl,則調用AuthenticatingFilter.executeLogin()

如果不是,則使request重定向到loginUrl,並return false;

 

AuthenticatingFilter.executeLogin():

調用subject.login(token)

如果登錄成功,則調用onLoginSuccess()

失敗則調用onLoginFailure()

 

AuthenticatingFilter.onLoginSuccess(): return true;

AuthenticatingFilter.onLoginFailure(): return false;

 

subject.login(token):

最終會調用realmdoGetAuthenticationInfo


思路

重寫默認的FormAuthenticationFilter,

在onAccessDeny()方法中:

如果請求的是loginUrl,則調用AuthenticatingFilter.executeLogin()

如果不是,則返回json,提示“未登錄,無法訪問該地址

 @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (this.isLoginRequest(request, response)) {
            if (this.isLoginSubmission(request, response)) {
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }

                return this.executeLogin(request, response);
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
            }
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            PrintWriter out = response.getWriter();
            ServerResponse serverResponse = ServerResponse.createByErrorMessage("未登錄,無法訪問該地址");
            Gson gson = GsonFactory.getGson();
            String s = gson.toJson(serverResponse);
            out.println(s);
            out.flush();
            out.close();
            return false;
        }
    }

由於AuthenticatingFilter.executeLogin()會調用onLoginSuccess()和onLoginFailure()方法

所以我們重寫兩個方法,前者返回登錄成功的json,並把當前用戶放入session;後者返回登錄失敗的json

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        PrintWriter out = null;
        try {
            out = response.getWriter();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        HttpSession session = ((HttpServletRequest)request).getSession();
        User user = userMapper.selectByUserName(token.getPrincipal().toString());
        session.setAttribute(Const.CURRENT_USER, user);
        ServerResponse serverResponse = ServerResponse.createBySuccessMsg("登錄成功");
        Gson gson = GsonFactory.getGson();
        String s = gson.toJson(serverResponse);
        out.println(s);
        out.flush();
        out.close();
        return true;
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        PrintWriter out = null;
        try {
            out = response.getWriter();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        ServerResponse serverResponse = ServerResponse.createByErrorMessage("登錄失敗");
        Gson gson = GsonFactory.getGson();
        String s = gson.toJson(serverResponse);
        out.println(s);
        out.flush();
        out.close();
        return false;
    }
}

最後配置shiro.xml將authc對應的默認的FormAuthenticationFilter,替換成我們的MyAuthenticationFilter

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/user/login.do"/>
        <property name="unauthorizedUrl" value="/user/unauthorized_err"/>
        <property name="filters">
            <map>
                <entry key="authc" value-ref="myAuthenticationFilter"/>
            </map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /user/login_err.do = anon
                /user/unauthorized_err.do = anon
                /** = authc
            </value>
        </property>
    </bean>
 <bean id="myAuthenticationFilter" class="com.mmall.shiro.filter.MyAuthenticationFilter"/>

方法二:使用PassThruAuthenticationFilter代替FormAuthenticationFilter

原理:
org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter源碼:
public class PassThruAuthenticationFilter extends AuthenticationFilter {
    public PassThruAuthenticationFilter() {
    }

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if(this.isLoginRequest(request, response)) {
            return true;
        } else {
            this.saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }
}
官方文檔:
An authentication filter that redirects the user to the login page when they are trying to access a protected resource. However, if the user is trying to access the login page, the filter lets the request pass through to the application code.


The difference between this filter and the FormAuthenticationFilter is that on a login submission (by default an HTTP POST to the login URL), the FormAuthenticationFilter filter attempts to automatically authenticate the user by passing the username and password request parameter values to Subject.login(usernamePasswordToken) directly.


Conversely, this controller always passes all requests to the loginUrl through, both GETs and POSTs. This is useful in cases where the developer wants to write their own login behavior, which should include a call to Subject.login(AuthenticationToken) at some point. For example, if the developer has their own custom MVC login controller or validator, this PassThruAuthenticationFilter may be appropriate.


我們繼承PassThruAuthenticationFilter並重寫onAccessDenied方法,和redirectToLogin方法

public class MyPassThruAuthenticationFilter extends PassThruAuthenticationFilter {

    private String loginErrUrl ="/";


    public void setLoginErrUrl(String loginErrUrl) {
        this.loginErrUrl = loginErrUrl;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (this.isLoginRequest(request, response)) {
            return true;
        } else {
            this.saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }
    //重寫redirectToLogin方法是因爲saveRequestAndRedirectToLogin方法會調用它,而原始的redirectToLogin方法會使得請求重定向到loginUrl
    @Override
    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        WebUtils.issueRedirect(request, response, loginErrUrl);
    }

}


首先修改shiro.xml , 將authc默認對應的FormAuthenticationFilter修改爲MyPassThruAuthenticationFilter

 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/user/login.do"/>
        <property name="unauthorizedUrl" value="/user/unauthorized_err"/>
        <property name="filters">
            <map>
                <entry key="authc">
                    <bean class="com.mmall.shiro.filter.MyPassThruAuthenticationFilter">
                        <property name="loginErrUrl" value="/user/login_err.do"/><!-- 注意這裏的loginErrUrl與上面的loginUrl的區別 -->
                    </bean>
                </entry>
            </map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                / = anon
                /user/login_err.do = anon
                /user/unauthorized_err.do = anon
                /user/logout.do = logout
                /** = authc
            </value>
        </property>
    </bean>

然後修改loginUrl對應的Contoller的方法,在其中要調用subject.login()完成shiro的認證

    //UserController中:結合shiro,使用PassThruAuthenticationFilter的登錄,需要調用subject.login()完成shiro的認證
    @RequestMapping(value = "login.do",method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> login(@RequestBody User user , HttpSession session) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(  user.getUsername(), user.getPassword());
//        ServerResponse serverResponse = iUserService.login(user.getUsername(), user.getPassword());
        try {
            /**subject.login(token) 提交申請,驗證能不能通過,也就是交給shiro。這裏會回調reaml(或自定義的realm)裏的一個方法
             protected AuthenticationInfo doGetAuthenticationInfo() */
            subject.login(token);
        } catch (AuthenticationException e) { //驗證身份失敗
            return ServerResponse.createByErrorMessage("登陸客戶身份失敗!");
        }

        /**Shiro驗證後,跳轉到此處,這裏判斷驗證是否通過 */
        if(subject.isAuthenticated()){  //驗證身份通過
            session.setAttribute(Const.CURRENT_USER,subject.getPrincipal());
            return ServerResponse.createBySuccessMsg("登錄成功");
        }else{
            return ServerResponse.createByErrorMessage("登陸客戶身份失敗!");
        }
    }

而loginErrUrl對應的方法則返回Json提示未登錄:

    @RequestMapping(value = "login_err.do",method = RequestMethod.GET)
    @ResponseBody
    public ServerResponse<User> login_error() {
        return ServerResponse.createByErrorMessage("用戶未登錄");
    }

參考: http://blog.csdn.net/a1314517love/article/details/38854057

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章