前后端分离架构使用shiro框架进行登录的两种实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012364631/article/details/79367181

方法一:重写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()

扫描二维码关注公众号,回复: 3811004 查看本文章

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

猜你喜欢

转载自blog.csdn.net/u012364631/article/details/79367181