26.SpringSecurity-重構用戶名密碼登陸

前言

image.png

  1. 咱們上一節對獲取令牌最終產生令牌的流程有了必定了解。 那麼在咱們本身的業務場景,本身寫的登陸這個流程裏面;咱們藉助於TokenEndPoint到TokenGranter是不能用的。由於上面發起的是獲取令牌的請求,而咱們是發起的登陸請求。
  2. 咱們要用咱們本身的過濾器去處理登陸請求,上面一直到TokenGranter這一步使用4種方式去去生成咱們的令牌。咱們都是不能用的,咱們須要用的就是令牌的服務產生令牌;

image.png
3.那麼在哪裏去運用上面AuthorizationServerTokenServices呢?是在咱們的:AuthenticationSuccessHandler裏面。以下圖:
image.png前端

4.咱們以前在spring-security-web項目裏面寫過AuthenticationSuccessHandler實現類的處理邏輯:若是是json登陸,咱們吧登陸成功信息authentication直接以json形式返回web

@Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperties securityProperties;


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        /**
         * 將Authentication轉換成json返回給前端
         * 參數:authentication 使用不一樣登陸方式,其值是不同的,這是一個接口在實際運轉中,他會傳不一樣的實現對象過來
         */
        logger.info("登陸成功");
        if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){//JSON異步登陸
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        }else {
            //非json就是使用父類處理器---父類處理器就是跳轉
            super.onAuthenticationSuccess(request,response,authentication);
        }
    }
}

在app端時候,咱們在登陸認證成功以後,咱們返回的再也不是用戶的認證信息,而是:用TokenServices生成的OAuth2AccessToken;從上一節咱們知道,咱們若是要生成OAuth2AccessToken就須要OAuth2Authentication;生成OAuth2Authentication咱們又須要OAuth2Request和Authentication。以前在Spring Security OAuth中是根據TokenGranter的不一樣實現模式(根據傳遞的grant_type)來構建生成OAuth2Authentication咱們又須要OAuth2Request和Authentication。可是在咱們登陸成功處理器裏面的方法參數上就有一個:Authentication authentication;因此在咱們的代碼邏輯裏面是不須要建立Authentication的業務邏輯的(因其在登陸時候就已經建立好了),咱們只須要建立處理OAuth2Request。spring

  1. OAuth2Request的構建成了咱們建立OAuth2AccessToken的關鍵。從上一節知道咱們建立OAuth2Request須要參數:ClientDetails和TokenRequest。ClientDetails從ClientDetailsService根據ClientId獲取。ClientId從哪裏來?從咱們請求參數裏面Header的Authorization裏面來。咱們經過解析請求頭中Authorization對應的字符串是能夠拿到ClientId的。 TokenRequest是根據ClientDetails和請求paramaters來new出一個對象的。

內容

前言總結三點:json

  1. 寫代碼的位置在哪?在咱們的MyAuthenticationSuccessHandler的onAuthenticationSuccess裏面。
  2. 最終目標是什麼?最終目標是構建出:OAuth2Request進而構建出OAuth2AccessToken
  3. 入手源頭在哪?入手源頭是從:請求參數裏面獲取clientId。

1.解析請求頭裏面的clientId

咱們參考Spring Security OAuth2咱們知道,咱們使用BasicAuthenticationFilter解析 安全

image.png

MyAuthenticationSuccessHandler代碼:服務器

@Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        /**
         * 將Authentication轉換成json返回給前端
         * 參數:authentication 使用不一樣登陸方式,其值是不同的,這是一個接口在實際運轉中,他會傳不一樣的實現對象過來
         */
        logger.info("登陸成功");

        //1.解析clientId
        String header = request.getHeader("Authorization");
        if (header == null && !header.startsWith("Basic ")) {
            throw new UnapprovedClientAuthenticationException("請求頭中無clientId信息");
        }
        String[] tokens = extractAndDecodeHeader(header, request);
        assert tokens.length == 2;
        String clientId = tokens[0];
        String clientSecret = tokens[1];


        //2.獲取ClientDetails並做校驗
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        if(null == clientDetails){//說明咱們根據配置的yxm拿不到第三方ClientDetails信息。咱們應該拋出異常
          throw new UnapprovedClientAuthenticationException("clientId對應的配置信息不存在:"+clientId);
        }else if(!StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){//若是clientDetails存在,咱們就應該校驗clientSeret
            throw new UnapprovedClientAuthenticationException("clientSecret不匹配:"+clientSecret);
        }

        //3.獲取TokenRequest:第一個參數map主要是爲了建立Authentication;可是上面Authentication已經建立。咱們能夠將其設置爲空
        //由於grantType爲:受權類型 以前是:password、authentication_code、implit、Client Credential、咱們這裏爲自定義模式
        /**
         * 類:ResourceOwnerPasswordTokenGranter
         *
         * protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
         *         Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
         *         String username = (String)parameters.get("username");
         *         String password = (String)parameters.get("password");
         *         parameters.remove("password");
         *         Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
         * }
         */
        TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP,clientId, clientDetails.getScope(), "custom");

        //4.建立OAuth2Request
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
        //5.建立OAuth2Authentication
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
        OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);

        //6.響應返回
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(accessToken));
    }

    private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
        byte[] base64Token = header.substring(6).getBytes("UTF-8");
        byte[] decoded;
        try {
            decoded = Base64.decode(base64Token);
        } catch (IllegalArgumentException var7) {
            throw new BadCredentialsException("Failed to decode basic authentication token");
        }

        String token = new String(decoded, "UTF-8");
        int delim = token.indexOf(":");
        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        } else {
            return new String[]{token.substring(0, delim), token.substring(delim + 1)};
        }
    }
}

2.安全配置

有了上面其實仍是不夠的,資源服務器目前只是用了Spring Security Oauth2的默認配置。以前咱們寫spring-security-web項目的時候的安全配置:WebSecurityConfig主配置類中,全部的配置都是在configure裏面的。如今同理咱們也須要一個配置。也就是咱們註解:@EnableResourceServer修飾的類。app

@Configuration
@EnableResourceServer
public class MyResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig; //短信驗證碼受權配置
    @Autowired
    private SpringSocialConfigurer mySocialSecurityConfig;
    @Autowired
    private SecurityProperties securityProperties;


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
                .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
                .successHandler(myAuthenticationSuccessHandler)
                .failureHandler(myAuthenticationFailureHandler);

        http.apply(smsCodeAuthenticationSecurityConfig)
                .and()
                .apply(mySocialSecurityConfig)//配置第三方social
                .and()
                .authorizeRequests()
                .antMatchers(
                        SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
                        SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
                        securityProperties.getBrowser().getLoginPage(),
                        SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",
                        securityProperties.getBrowser().getSignUpUrl())
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().disable();
    }
}

3.啓動項目 測試驗證

在spring-security-demo項目咱們以前是訪問:"/authentication/form"來訪問登陸的。咱們專門寫了頁面,如今咱們使用app端登陸,咱們使用:restlet_client提交請求。咱們書寫用戶名和密碼。
組織請求參數:
image.png異步

image.png

咱們攜帶這個access_token去訪問用戶信息:ide

{
 "access_token": "4f04305c-a956-4546-95ba-20ff8d9ed2e3",
 "token_type": "bearer",
 "refresh_token": "76abf98d-dbe5-4f2e-8f18-cf83c6f3a2c5",
 "expires_in": 43199
}

image.png

4.總結

其實咱們如今已經達到咱們的要求了。app去調用咱們自個表單登陸的服務請求。而後給他返回一個OAuth2AccessToken。而後他拿着這個Access_Token再去請求相應的資源。測試

相關文章
相關標籤/搜索