javascript
搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【密码认证流程】 总结
在搭建介紹流程之前,確保您已經搭建了一個 Eureka 注冊中心,因為沒有注冊中心的話會報錯(也有可能我搭建的認證服務器是我項目的一個子模塊的原因):Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}
http://localhost:8761/eureka/ 是因為配置文件未提供注冊中心地址,springcloud 默認的注冊中心地址就是這個
另外:文末會提供所有代碼
Oauth2.0 有以下四種授權模式:本文介紹 密碼認證
1、授權碼模式(Authorization Code)[常用]
2、隱式授權模式(Implicit)[不常用]
3、密碼模式(Resource Owner Password Credentials)[常用]
4、客戶端模式(Client Credentials)[不常用]
密碼認證流程
密碼模式(Resource Owner Password Credentials)與授權碼模式的區別是申請令牌不再使用授權碼,而是直接 通過用戶名和密碼即可申請令牌。
Post請求:http://localhost:9001/oauth/token
參數:
grant_type:密碼模式授權填寫password
username:賬號
password:密碼
并且此鏈接需要使用 http Basic認證。
所以密碼認證流程相對來說比較簡單的
前期工作:接下來的截圖我默認你已經看過我上一篇博客 授權碼認證流程,因為有些代碼是在前一篇的基礎上
在 UserDetailsServiceImpl 的 loadUserByUsername 方法后面
如下代碼塊
這樣我們默認的賬戶是任意賬戶名,密碼固定為 qkm19981013,接下來使用postman測試
基本上密碼認證流程就是這樣,但是這有一個問題,我們每次輸入我們的用戶名和密碼的時候,我們都需要帶上HttpBasic認證的客戶端ID和密鑰,但是實際上我們的ID和密鑰是不允許透露出去的,這很不安全,所以接下來我們改造這種模式,客戶端只需要提供用戶名和密碼,HttpBasic認證信息由我們服務端提供,步驟流程分為以下幾步:
Service 層業務
public interface AuthService {/*** 登錄,授權認證方法*/AuthToken login(String username, String password, String clientId, String clientSecret); } @Service public class AuthServiceImpl implements AuthService {@Autowiredprivate LoadBalancerClient loadBalancerClient;private RestTemplate restTemplate;private ClientHttpRequestFactory factory;@Autowiredpublic void setFactory() {SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();factory.setReadTimeout(5000);//msfactory.setConnectTimeout(15000);//msthis.factory = factory;}@Autowiredpublic void setRestTemplate() {this.restTemplate = new RestTemplate(this.factory);}@Overridepublic AuthToken login(String username, String password, String clientId, String clientSecret) {//申請令牌return applyToken(username,password,clientId, clientSecret);}/*** 認證方法* @param username:用戶登錄名字* @param password:用戶密碼* @param clientId:配置文件中的客戶端ID* @param clientSecret:配置文件中的秘鑰* @return AuthToken*/private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {//選中認證服務的地址ServiceInstance serviceInstance = loadBalancerClient.choose("user-auth");if (serviceInstance == null) {throw new RuntimeException("找不到對應的服務");}//獲取令牌的urlString path = serviceInstance.getUri().toString() + "/oauth/token";//定義bodyMultiValueMap<String, String> formData = new LinkedMultiValueMap<>();//授權方式formData.add("grant_type", "password");//賬號formData.add("username", username);//密碼formData.add("password", password);//定義頭MultiValueMap<String, String> header = new LinkedMultiValueMap<>();header.add("Authorization", httpBasic(clientId, clientSecret));//指定 restTemplate當遇到400或401響應時候也不要拋出異常,也要正常返回值restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {@Overridepublic void handleError(@NonNull ClientHttpResponse response) throws IOException {//當響應的值為400或401時候也要正常響應,不要拋出異常if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {super.handleError(response);}}});@SuppressWarnings("all")Map map;try {@SuppressWarnings("all")//http請求spring security的申請令牌接口ResponseEntity<Map> mapResponseEntity = restTemplate.exchange(path, HttpMethod.POST, new HttpEntity<>(formData, header), Map.class);//獲取響應數據map = mapResponseEntity.getBody();} catch (RestClientException e) {throw new RuntimeException(e);}if (map == null || map.get("access_token") == null || map.get("refresh_token") == null || map.get("jti") == null) {//jti是jwt令牌的唯一標識作為用戶身份令牌throw new RuntimeException("創建令牌失敗!");}//將響應數據封裝成AuthToken對象AuthToken authToken = new AuthToken();//訪問令牌(jwt)String accessToken = (String) map.get("access_token");//刷新令牌(jwt)String refreshToken = (String) map.get("refresh_token");//jti,作為用戶的身份標識String jwtToken = (String) map.get("jti");authToken.setJti(jwtToken);authToken.setAccessToken(accessToken);authToken.setRefreshToken(refreshToken);return authToken;}/*** base64編碼* @param clientId 客戶端ID* @param clientSecret 客戶端密鑰* @return String*/private String httpBasic(String clientId, String clientSecret) {//將客戶端id和客戶端密碼拼接,按“客戶端id:客戶端密碼”String string = clientId + ":" + clientSecret;//進行base64編碼byte[] encode = Base64Utils.encode(string.getBytes());return "Basic " + new String(encode); //注意 "Basic " 英文后面有個空格} }//將客戶端id和客戶端密碼拼接,按“客戶端id:客戶端密碼” 關于為啥這樣定義,且采用Base64 編碼格式,如下圖
- Basic 是明文,沒有加密
- 我們將Basic后面的密文拷貝出來,使用 Base64 解密一下看看
Utils 工具
@Data public class AuthToken implements Serializable{/*** 令牌信息*/String accessToken;/*** 刷新token(refresh_token)*/String refreshToken;/*** jwt短令牌*/String jti;} public class CookieUtil {/*** 設置cookie** @param response response* @param name cookie名字* @param value cookie值* @param maxAge cookie生命周期 以秒為單位*/public static void addCookie(HttpServletResponse response, String domain, String path, String name,String value, int maxAge, boolean httpOnly) {Cookie cookie = new Cookie(name, value);cookie.setDomain(domain);cookie.setPath(path);cookie.setMaxAge(maxAge);cookie.setHttpOnly(httpOnly);response.addCookie(cookie);}/*** 根據cookie名稱讀取cookie* @param request request* @return map<cookieName,cookieValue>*/public static Map<String,String> readCookie(HttpServletRequest request, String ... cookieNames) {Map<String,String> cookieMap = new HashMap<String,String>();Cookie[] cookies = request.getCookies();if (cookies != null) {for (Cookie cookie : cookies) {String cookieName = cookie.getName();String cookieValue = cookie.getValue();for (String name : cookieNames) {if (name.equals(cookieName)) {cookieMap.put(cookieName, cookieValue);}}}}return cookieMap;} } /*** 繼承了 org.springframework.security.core.userdetails.User,* 相當于對其的擴展,可以寫我們需要添加的屬性*/ @SuppressWarnings("unused") public class UserJwt extends User {private String id; //用戶IDprivate String name; //用戶名字public UserJwt(String username, String password, Collection<? extends GrantedAuthority> authorities) {super(username, password, authorities);}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;} }VO 對象
@Data public class LoginVo {@NotBlank(message = "用戶名不能為空")private String username;@NotBlank(message = "密碼不能為空")private String password; }Controller 層
@RestController @RequestMapping(value = "/user") public class AuthController {/*** 客戶端ID*/@Value("${auth.clientId}")private String clientId;/*** 秘鑰*/@Value("${auth.clientSecret}")private String clientSecret;/*** Cookie存儲的域名*/@Value("${auth.cookieDomain}")private String cookieDomain;/*** Cookie生命周期*/@Value("${auth.cookieMaxAge}")private int cookieMaxAge;@Autowiredprivate AuthService authService;@PostMapping("/login")public Result<Object> login(@RequestBody @Validated LoginVo loginVo) {//申請令牌AuthToken authToken;try {authToken = authService.login(loginVo.getUsername(), loginVo.getPassword(), clientId, clientSecret);} catch (Exception e) {return new Result<>(false, StatusCode.ERROR, e.getMessage());}//用戶身份令牌String access_token = authToken.getAccessToken();//將令牌存儲到cookiesaveCookie(access_token);return new Result<>(true, StatusCode.OK, "登錄成功!");}/*** 將令牌存儲到cookie** @param token token*/private void saveCookie(String token) {HttpServletResponse response = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse();assert response != null;CookieUtil.addCookie(response, cookieDomain, "/", "Authorization", token, cookieMaxAge, false);} }代碼全部貼完了,接下來我們進行測試
再次發送請求
到此我們僅使用 用戶名和密碼 就進行了授權操作,保證了服務端的一定的安全性
總結
以上是生活随笔為你收集整理的搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【密码认证流程】 总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 搭建认证服务器 - Spring Sec
- 下一篇: SpringCloud分布式事务,版本一