1. 程式人生 > >spring boot整合shiro之shiro過濾器介紹

spring boot整合shiro之shiro過濾器介紹

過濾器鏈條配置說明

  • 1、一個URL可以配置多個Filter,使用逗號分隔
  • 2、當設定多個過濾器時,全部驗證通過,才視為通過
  • 3、部分過濾器可指定引數,如perms,roles

Shiro內建的FilterChain

  • anon(org.apache.shiro.web.filter.authc.AnonymousFilter):例子/admins/**=anon 沒有引數,表示可以匿名使用。
  • authc(org.apache.shiro.web.filter.authc.FormAuthenticationFilter):例如/admins/user/**=authc表示需要認證(登入)才能使用,沒有引數
  • authcBasic(org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter):例如/admins/user/**=authcBasic,沒有引數表示httpBasic認證
  • perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter:(許可權)例子/admins/user/=perms[user:add:],引數可以寫多個,多個時必須加上引號,並且引數之間用逗號分割,例如/admins/user/=perms[“user:add:,user:modify:*”],當有多個引數時必須每個引數都通過才通過,想當於isPermitedAll()方法。
  • port org.apache.shiro.web.filter.authz.PortFilter:例子/admins/user/**=port[8081],當請求的url的埠不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裡port的埠,queryString
  • rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter:例子/admins/user/=rest[user],根據請求的方法,相當於/admins/user/
    =perms[user:method] ,其中method為post,get,delete等。
  • roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter:(角色)例子/admins/user/=roles[admin],引數可以寫多個,多個時必須加上引號,並且引數之間用逗號分割,當有多個引數時,例如admins/user/=roles[“admin,guest”],每個引數通過才算通過,相當於hasAllRoles()方法。
  • ssl org.apache.shiro.web.filter.authz.SslFilter:例子/admins/user/**=ssl沒有引數,表示安全的url請求,協議為https
  • user org.apache.shiro.web.filter.authc.UserFilter:例如/admins/user/**=user沒有引數表示必須存在使用者,當登入操作時不做檢查

配置注意事項

  • /admin?=authc 表示可以請求以admin開頭的字串,如xxx/adminfefe,但無法匹配多個,即xxx/admindf/admin是不行的
  • /admin*=authc 表示可以匹配零個或者多個字元,如/admin,/admin1,/admin123,但是不能匹配/admin/abc這種
  • /admin/**=authc 表示可以匹配零個或者多個路徑,如/admin,/admin/ad/adfdf等
  • /login=anon 和 /login=anon/ 不一樣

自定義過濾器介紹

  • 過濾器一般實現org.apache.shiro.web.filter.authc.AuthenticatingFilter類。
  • 方法使用說明
package cn.xo68.boot.auth.server.shiro.filter;

import java.io.IOException;

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

import cn.xo68.boot.auth.core.domain.OAuth2AuthenticationToken;
import cn.xo68.boot.auth.core.domain.Oauth2Principal;
import cn.xo68.boot.auth.core.properties.OAuthResourceProperties;
import cn.xo68.boot.auth.server.properties.AuthServerProperties;
import cn.xo68.core.util.StringTools;
import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.URLConnectionClient;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.OAuthAccessTokenResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.apache.oltu.oauth2.common.message.types.ParameterStyle;
import org.apache.oltu.oauth2.rs.request.OAuthAccessResourceRequest;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.subject.WebSubject;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

/**
 * oauth2 認證過濾器
 * @author wuxie
 * @date 2018-8-5
 */
public class OAuth2AuthenticationFilter extends AuthenticatingFilter {

    private static  final Logger logger= LoggerFactory.getLogger(OAuth2AuthenticationFilter.class);

    private SimpleCookie accessTokenCookie;

    private AuthServerProperties authServerProperties;
    private OAuthResourceProperties oAuthResourceProperties;

    //oauth2 authc code引數名
    private String authcCodeParam = "code";
    //客戶端id
    private String clientId;
    //伺服器端登入成功/失敗後重定向到的客戶端地址
    private String redirectUrl;
    //oauth2伺服器響應型別
    private String responseType = "code";

    private String failureUrl;

    public SimpleCookie getAccessTokenCookie() {
        return accessTokenCookie;
    }

    public void setAccessTokenCookie(SimpleCookie accessTokenCookie) {
        this.accessTokenCookie = accessTokenCookie;
    }

    public AuthServerProperties getAuthServerProperties() {
        return authServerProperties;
    }

    public void setAuthServerProperties(AuthServerProperties authServerProperties) {
        this.authServerProperties = authServerProperties;
    }

    public OAuthResourceProperties getoAuthResourceProperties() {
        return oAuthResourceProperties;
    }

    public void setoAuthResourceProperties(OAuthResourceProperties oAuthResourceProperties) {
        this.oAuthResourceProperties = oAuthResourceProperties;
    }

    public void setAuthcCodeParam(String authcCodeParam) {
        this.authcCodeParam = authcCodeParam;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public void setRedirectUrl(String redirectUrl) {
        this.redirectUrl = redirectUrl;
    }

    public void setResponseType(String responseType) {
        this.responseType = responseType;
    }

    public void setFailureUrl(String failureUrl) {
        this.failureUrl = failureUrl;
    }
    /**
    * 如命名字面意思,根據請求,生成一個令牌。OAuth2AuthenticationToken是我自定義的
    */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        try{
            OAuthAccessResourceRequest oauthRequest = new OAuthAccessResourceRequest(httpRequest, ParameterStyle.HEADER, ParameterStyle.QUERY);

            OAuth2AuthenticationToken oAuth2AuthenticationToken=new OAuth2AuthenticationToken();
            Oauth2Principal oauth2Principal=new Oauth2Principal();
            oAuth2AuthenticationToken.setPrincipal(oauth2Principal);

            //令牌
            String accessToken = oauthRequest.getAccessToken();
            if(StringTools.isEmpty(accessToken)){
                accessToken=accessTokenCookie.getValue();
            }
            if(StringTools.isNotEmpty(accessToken)){
                oAuth2AuthenticationToken.setCredential(accessToken);
                oauth2Principal.setAccessToken(accessToken);
                return oAuth2AuthenticationToken;
            }else {
                //authorize_code
                String code = httpRequest.getParameter(authcCodeParam);
                if(StringTools.isNotEmpty(code)){
                    //換令牌
                    //accessToken="";
                    //oAuth2AuthenticationToken.setAccessToken(accessToken);
                    //return oAuth2AuthenticationToken;
                    OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
                    OAuthClientRequest accessTokenRequest = OAuthClientRequest
                            .tokenLocation(oAuthResourceProperties.getAccessTokenUrl())
                            .setGrantType(GrantType.AUTHORIZATION_CODE)
                            .setClientId(oAuthResourceProperties.getClientId())
                            .setClientSecret(oAuthResourceProperties.getClientSecret())
                            .setCode(code)
                            .setRedirectURI(redirectUrl)
                            .buildQueryMessage();

                    OAuthAccessTokenResponse oAuthResponse = oAuthClient.accessToken(accessTokenRequest, OAuth.HttpMethod.POST);
                    accessToken=oAuthResponse.getAccessToken();
                    oAuth2AuthenticationToken.setCredential(accessToken);
                    oauth2Principal.setAccessToken(accessToken);
                    return oAuth2AuthenticationToken;
                }
            }
        }catch (OAuthProblemException e){
            logger.warn("過濾器中獲取令牌令牌異常", e);
        }
        return new OAuth2AuthenticationToken();
    }
    /**
    * 根據請求資訊,引數等資訊判斷是否允許通過,如果返回false,則是不通過。最終是否去訪問web處理,有isAccessAllowed,onAccessDenied方法共同或運算決定,也就是隻要有一個是true就會訪問web控制器或action。
    */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return false;
    }
    /**
    *根據請求,拒絕通過處理,如果返回false,則不再去訪問web控制器或action
    */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {


        String error = request.getParameter("error");
        String errorDescription = request.getParameter("error_description");
        if(!StringUtils.isEmpty(error)) {//如果服務端返回了錯誤,也就是服務端裡檢查不通過進入if裡返回的錯誤
            WebUtils.issueRedirect(request, response, authServerProperties.getUnauthorizedUrl() + "?error=" + error + "error_description=" + errorDescription);
            return false;
        }

//        Subject subject = getSubject(request, response);
//        if(!subject.isAuthenticated()) {
//            if(StringUtils.isEmpty(request.getParameter(authcCodeParam))) {
//                //如果使用者沒有身份驗證,且沒有auth code,則重定向到服務端授權,即訪問AuthorizeController的authorize方法
//                saveRequestAndRedirectToLogin(request, response);
//                return false;
//            }
//        }

        return executeLogin(request, response);
    }

/**
* 登入驗證處理,父類本來就有,只是有個bug,按官方給出的方法進行了重寫
*/
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        AuthenticationToken token = this.createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        } else {
            try {
                //修復bug程式碼,也算個不小的坑吧
                Subject subject =  new WebSubject.Builder(request, response).buildSubject();
                subject.login(token);
                ThreadContext.bind(subject);
                return this.onLoginSuccess(token, subject, request, response);
            } catch (AuthenticationException var5) {
                return this.onLoginFailure(token, var5, request, response);
            }
        }
    }

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
                                     ServletResponse response) throws Exception {
        //issueSuccessRedirect(request, response);
        Subject msubject=subject;
        return true;
    }
    /**
    *登入失敗處理(認證令牌驗證失敗)
    */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,
                                     ServletResponse response) {
        Subject subject = getSubject(request, response);
        if (subject.isAuthenticated() || subject.isRemembered()) {
            try {
                issueSuccessRedirect(request, response);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            try {
                WebUtils.issueRedirect(request, response, authServerProperties.getUnauthorizedUrl());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }
}