1. 程式人生 > 實用技巧 >SpringSecurity+Oauth2 JWT安全框架(二)

SpringSecurity+Oauth2 JWT安全框架(二)

一、登入介面具體實現

完成上一篇測試以後,表示我們搭建的認證伺服器可以生成令牌,這時候我們需要再此基礎上寫登入邏輯

登入介面

前端post提交賬號、密碼等,使用者身份效驗通過,生成令牌,並將令牌儲存到redis當中,具體業務流程圖如下

建立AuthControllerApi.java
@Api(tags = "使用者認證API介面", description = "使用者登入認證介面")
public interface AuthControllerApi {
    @ApiOperation(value = "登入")
    @PostMapping("/userLogin")
    
public ResultVo login(LoginRequest loginRequest); @ApiOperation(value = "退出") public ResultVo logout(); }

建立AuthController.java

@RestController
public class AuthController implements AuthControllerApi{
    @Autowired
    private AuthService authService;

    @Autowired
    private SecurityAuth securityAuth;

    @Override
    @PostMapping(
"/userLogin") public ResultVo login(LoginRequest loginRequest) { //申請令牌 ResultVo resultVo=authService.login(loginRequest,securityAuth.getClientId(),securityAuth.getClientSecret()); return resultVo; } }

建立AuthService.java

@Service
public class AuthService {

    @Autowired
    
private StringRedisTemplate stringRedisTemplate; @Autowired private SecurityAuth securityAuth; public ResultVo login(LoginRequest loginRequest, String clientId, String clientSecret) { //使用者賬號和密碼的非空判斷 if (!Exist.isExists(loginRequest) || !Exist.isExists(loginRequest.getPassword())) { return ResultVo.response(AuthKeFeiEnums.FAIL_NULL_PASSWORD); } if (!Exist.isExists(loginRequest) || !Exist.isExists(loginRequest.getUsername())) { return ResultVo.response(AuthKeFeiEnums.FAIL_NULL_USERNAME); } //獲取客戶賬號和密碼 String username = loginRequest.getUsername(); String password = loginRequest.getPassword(); //請求security獲取令牌 AuthToken authToken = applyToken(username, password, clientId, clientSecret); if (!Exist.isExists(authToken)) { return ResultVo.response(AuthKeFeiEnums.FAIL_AUTH_APPLYTOKEN_NULL); } //獲取使用者身份令牌 String access_token = authToken.getAccess_token(); //儲存到redis中的內容 String jsonString = JSON.toJSONString(authToken); //將令牌儲存到redis當中 boolean result = this.saveToken(access_token, jsonString, securityAuth.getTokenValiditySeconds()); if (!result) { return ResultVo.response(AuthKeFeiEnums.FAIL_SAVE_AUTH_TOKEN); } //將令牌儲存到cookie addCookie(securityAuth.getCookieDomain(), "/", "uid", access_token, securityAuth.getCookieMaxAge(), false); return ResultVo.response(AuthKeFeiEnums.SUCCESS, access_token); }

/**
     * @param clientId     客戶端賬號
     * @param clientSecret 客戶端密碼
     * @return java.lang.String
     * @author GuFei
     * <獲取httpbasic的串>
     * @date 2019/8/28 11:52
     * @version V1.0
     */
    private String getHttpBasic(String clientId, String clientSecret) {
        String ss = clientId + ":" + clientSecret;
        byte[] encode = Base64Utils.encode(ss.getBytes());
        //Basic後面要空一個
        return "Basic " + new String(encode);
    }

    /**
     * @param username     使用者賬號
     * @param password     使用者密碼
     * @param clientId     客戶端賬號
     * @param clientSecret 客戶端密碼
     * @return com.kefei.fremawork.dto.po.security.AuthToken
     * @author GuFei
     * <申請令牌>
     * @date 2019/8/28 11:53
     * @version V1.0
     */
    private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {
        RestTemplate restTemplate = new RestTemplate();
        //定義header
        LinkedMultiValueMap<String, String> header = new LinkedMultiValueMap<>();
        String httpBasic = getHttpBasic(clientId, clientSecret);
        header.add("Authorization", httpBasic);

        //定義body
        LinkedMultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "password");
        body.add("username", username);
        body.add("password", password);

        HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(body, header);
        //String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables

        //設定restTemplate遠端呼叫時候,對400和401不讓報錯,正確返回資料
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
                    super.handleError(response);
                }
            }
        });

        ResponseEntity<Map> exchange = restTemplate.exchange(securityAuth.getAccessTokenUri(), HttpMethod.POST, httpEntity, Map.class);

        //申請令牌資訊
        Map bodyMap = exchange.getBody();
        if (bodyMap == null ||
                bodyMap.get("access_token") == null ||
                bodyMap.get("refresh_token") == null ||
                bodyMap.get("jti") == null) {
            return null;
        }
        AuthToken authToken = new AuthToken();
        //訪問令牌(jwt)
        String jwt_token = (String) bodyMap.get("access_token");
        //重新整理令牌(jwt)
        String refresh_token = (String) bodyMap.get("refresh_token");
        //jti,作為使用者的身份標識
        String access_token = (String) bodyMap.get("jti");
        authToken.setJwt_token(jwt_token);
        authToken.setAccess_token(access_token);
        authToken.setRefresh_token(refresh_token);
        return authToken;
    }

    /*
     * @author GuFei
     * <描述內容>
     * @date 2019/8/28 11:53
     * @param access_token 令牌
     * @param content 獲取到令牌、重新整理令牌、短令牌等資訊
     * @param ttl 儲存在redis中的有效時間
     * @return boolean
     * @version V1.0
     */
    private boolean saveToken(String access_token, String content, long ttl) {
        //令牌名稱key
        String nameKey = RedisKeyGenerator.saveTokenKey(access_token);
        //儲存令牌到redis,TimeUnit.SECONDS為設定過期時間,單位為秒
        stringRedisTemplate.boundValueOps(nameKey).set(content, ttl, TimeUnit.SECONDS);
        //獲取過期時間
        Long expire = stringRedisTemplate.getExpire(nameKey);
        //如果大於0,表示儲存成功,如果小於0則表示儲存失敗了
        return expire > 0;
    }

    /**
     * @param domain
     * @param path
     * @param name     cookie名字
     * @param value    cookie值
     * @param maxAge   cookie生命週期 以秒為單位
     * @param httpOnly
     * @return void
     * @author GuFei
     * <將令牌儲存到cookie>
     * @date 2019/8/28 11:55
     * @version V1.0
     */
    private void addCookie(String domain, String path, String name,
                           String value, int maxAge, boolean httpOnly) {
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        Cookie cookie = new Cookie(name, value);
        cookie.setDomain(domain);
        cookie.setPath(path);
        cookie.setMaxAge(maxAge);
        cookie.setHttpOnly(httpOnly);
        response.addCookie(cookie);
    }
}

注意:在使用postman測試的時候,一切正常但是正式環境測試異常報401,Request Method是options型別,這是因為web前端在傳送請求的時候,會先發送一個options請求,作為探測請求檢視請求連結是否建立。

解決辦法:

我們需要再資源服務裡面找到繼承ResourceServerConfigurerAdapter的實體類

給與放行

至此,我們完成了認證伺服器的登入認證請求