Problems and solutions of front and rear end separation of springboot+shiro

Posted by SecureMind on Sat, 05 Mar 2022 05:49:33 +0100

Problem description

In the process of upgrading the project to separate the front end from the back end, some problems have been encountered in shiro's permission control. Here are the problems and solutions recorded;

Cross domain problems after using springboot+shiro to separate the front and back ends

After the front end and back end of the project are separated, the requests sent from the front end to the back end always report cross domain problems
Access to XMLHttpRequest at 'http://localhost:8081/******' from origin 'http://localhost:9090' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.

The reason is that after the front and back ends of the project are separated, the front end will attach Authorization: ***************************************************************; To verify permissions, which is equivalent to customizing the header field. As a result, the OPTIONS request will be sent by default before the real request sent by the front end to detect the server. If the status 200 success message is returned, the actual normal POST / GET request will continue, otherwise the cross domain problem will be returned.
The OPTIONS request will not carry the authentication information, but will be intercepted by shiro's interceptor in the project, resulting in the failure of the OPTIONS request to return to the front end successfully, resulting in cross domain problems.

resolvent

Custom interceptor CorsFilter

@Override
    public void init(FilterConfig config) throws ServletException {
        this.config = config;
    }
    @Override
    public void destroy() {
        this.config = null;
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        response.setHeader( "Access-Control-Allow-Origin", request.getHeader("Origin"));
        // Methods to allow requests
        response.setHeader( "Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,PUT" );
        // How many seconds can the result be cached without sending a pre inspection request
        response.setHeader( "Access-Control-Max-Age", "3600" );
        // Indicates that it allows cross domain requests to contain xxx headers
        response.setHeader( "Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
        //Whether to allow the browser to carry user identity information (cookie s)
        response.setHeader( "Access-Control-Allow-Credentials", "true" );
        // response.setHeader( "Access-Control-Expose-Headers", "*" );
        if (request.getMethod().equals( "OPTIONS" )) {
            response.setStatus( 200 );
            return;
        }
        filterChain.doFilter( servletRequest, response );

All requests are allowed to cross domains. As long as the OPTIONS request returns success directly, and the priority of the interceptor CorsFilter should be set to the highest, the request will be processed at the interceptor CorsFilter first, otherwise it will be intercepted by shiro interceptor.

After the item is changed to front and back end separation, the status of various login and authentication returns

In the past, the front end of the project was jsp. All kinds of projects without access rights, jump login and logout return to a page and then jump. Now, after the front and back ends are separated, the status code agreed by the front end should be returned to let the front end control the jump of the page, which involves rewriting various filters of shiro

resolvent

If the user does not log in and accesses the requested connection, do you need to return to the front-end 403 state? Here, you need to override the onAccessDenied method in FormAuthenticationFilter
NewFormAuthenticationFilter:

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.");
                }
			WebUtils.toHttp(response).setContentType("application/json; charset=utf-8");
            WebUtils.toHttp(response).getWriter().print(new Response(403,"Please login"));
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
            }

            WebUtils.toHttp(response).setContentType("application/json; charset=utf-8");
            WebUtils.toHttp(response).getWriter().print(new Response(403,"Please login"));

            return false;
        }
    }

If the user accesses unauthorized resources, do you want to return the status 401? This is to override the onAccessDenied method in PermissionsAuthorizationFilter
NewPermissionsAuthorizationFilter:

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        WebUtils.toHttp(response).setContentType("application/json; charset=utf-8");
        WebUtils.toHttp(response).getWriter().print(new Response(401,"You do not have permission to access this resource"));
        return false;
    }

If the user logs out, it is necessary to return the login success information and override the preHandle method in LogoutFilter
NewLogoutFilter:

protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = this.getSubject(request, response);
        String redirectUrl = this.getRedirectUrl(request, response, subject);

        try {
            subject.logout();
            WebUtils.toHttp(response).setContentType("application/json; charset=utf-8");
            WebUtils.toHttp(response).getWriter().print(new Response(000000,"Successfully logged out")); 
        } catch (SessionException var6) {
            log.debug("Encountered session exception during logout.  This can generally safely be ignored.", var6);
        }
        return false;
    }

Some problems occurred when rewriting these three filters. After the filter was written and registered into the project, it was found that all connections were blocked, even the resources marked annon were no exception; Finally, it was found that the three interceptors were set to the singleton mode and put into the springboot container. In this way, there are not only three interceptors in shiro, but also these interceptors in spring, and the incoming requests were intercepted by the interceptors in spring.
Here, the interceptor registration is changed to the following form:

Map<String, Filter> filters = factoryBean.getFilters();
        filters.put("authc",new NewFormAuthenticationFilter());
        filters.put("logout",new NewLogoutFilter());
        filters.put("perms",new NewPermissionsAuthorizationFilter());
        factoryBean.setFilters(filters);

So you can only register in shiro.