Springboot?Vue實現(xiàn)單點登陸功能示例詳解
正文
登陸是系統(tǒng)最基礎(chǔ)的功能之一。這么長時間了,一直在寫業(yè)務(wù),這個基礎(chǔ)功能反而沒怎么好好研究,都忘差不多了。今天沒事兒就來擼一下。
以目前在接觸和學(xué)習(xí)的一個開源系統(tǒng)為例,來分析一下登陸該怎么做。代碼的話我就直接CV了。
簡單上個圖
(有水印。因為窮所以沒開會員)
先分析下登陸要做啥
首先,搞清楚要做什么。
登陸了,系統(tǒng)就知道這是誰,他有什么權(quán)限,可以給他開放些什么業(yè)務(wù)功能,他能看到些什么菜單?。。。這是這個功能的目的和存在的意義。
怎么落實?
怎么實現(xiàn)它?用什么實現(xiàn)?
我們的項目是Springboot + Vue前后端分離類型的。
選擇用token + redis 實現(xiàn),權(quán)限的話用SpringSecurity來做。
前后端分離避不開的一個問題就是單點登陸,單點登陸咱們有很多實現(xiàn)方式:CAS中央認證、JWT、token等,咱們這種方式其實本身就是基于token的一個單點登陸的實現(xiàn)方案。
單點登陸我們改天整理一篇OAuth2.0的實現(xiàn)方式,今天不搞這個。
上代碼
概念這個東西越說越玄。咱們直接上代碼吧。
接口:
@PostMapping("/login") public AjaxResult login(@RequestBody LoginBody loginBody) { AjaxResult ajax = AjaxResult.success(); // 生成令牌 //用戶名、密碼、驗證碼、uuid String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid()); ajax.put(Constants.TOKEN, token); return ajax; }
用戶信息驗證交給SpringSecurity
/** * 登錄驗證 */ public String login(String username, String password, String code, String uuid) { // 驗證碼開關(guān),順便說一下,系統(tǒng)配置相關(guān)的開關(guān)之類都緩存在redis里,系統(tǒng)啟動的時候加載進來的。這一塊兒的代碼就不貼出來了 boolean captchaEnabled = configService.selectCaptchaEnabled(); if (captchaEnabled) { //uuid是驗證碼的redis key,登陸頁加載的時候驗證碼生成接口返回的 validateCaptcha(username, code, uuid); } // 用戶驗證 -- SpringSecurity Authentication authentication = null; try { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); AuthenticationContextHolder.setContext(authenticationToken); // 該方法會去調(diào)用UserDetailsServiceImpl.loadUserByUsername。 // authentication = authenticationManager.authenticate(authenticationToken); } catch (Exception e) { if (e instanceof BadCredentialsException) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } else { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); throw new ServiceException(e.getMessage()); } } finally { AuthenticationContextHolder.clearContext(); } AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); recordLoginInfo(loginUser.getUserId()); // 生成token return tokenService.createToken(loginUser); }
把校驗驗證碼的部分貼出來,看看大概的邏輯(這個代碼封裝得太碎了。。。沒全整出來)
/** * 校驗驗證碼 */ public void validateCaptcha(String username, String code, String uuid) { //uuid是驗證碼的redis key String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); //String CAPTCHA_CODE_KEY = "captcha_codes:"; String captcha = redisCache.getCacheObject(verifyKey); redisCache.deleteObject(verifyKey); if (captcha == null) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); throw new CaptchaExpireException(); } if (!code.equalsIgnoreCase(captcha)) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); throw new CaptchaException(); } }
token生成部分
這里,token
/** * 創(chuàng)建令牌 */ public String createToken(LoginUser loginUser) { String token = IdUtils.fastUUID(); loginUser.setToken(token); setUserAgent(loginUser); refreshToken(loginUser); Map<String, Object> claims = new HashMap<>(); claims.put(Constants.LOGIN_USER_KEY, token); return createToken(claims); }
刷新token
/** * 刷新令牌 */ public void refreshToken(LoginUser loginUser) { loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); // 根據(jù)uuid將loginUser緩存 String userKey = getTokenKey(loginUser.getToken()); redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); }
驗證token
/** * 驗證令牌 */ public void verifyToken(LoginUser loginUser) { long expireTime = loginUser.getExpireTime(); long currentTime = System.currentTimeMillis(); if (expireTime - currentTime <= MILLIS_MINUTE_TEN) { refreshToken(loginUser); } }
注意這里返回給前端的token其實用JWT加密了一下,SpringSecurity的過濾器里有進行解析。
另外,鑒權(quán)時會刷新token有效期,看下面第二個代碼塊的注釋。
@Override protected void configure(HttpSecurity httpSecurity) throws Exception { //...無關(guān)的代碼刪了 httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); }
@Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private TokenService tokenService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { LoginUser loginUser = tokenService.getLoginUser(request); if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) { //刷新token有效期 tokenService.verifyToken(loginUser); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } chain.doFilter(request, response); } }
這個登陸方案里用了token + redis,還有JWT,其實用哪一種方案都可以獨立實現(xiàn),并且兩種方案都可以用來做單點登陸。
這里JWT只是起到個加密的作用,無它。
以上就是Springboot Vue實現(xiàn)單點登陸功能示例詳解的詳細內(nèi)容,更多關(guān)于Springboot Vue單點登陸的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決Mybatis-plus自定義TypeHandler查詢映射結(jié)果一直為null問題
這篇文章主要介紹了解決Mybatis-plus自定義TypeHandler查詢映射結(jié)果一直為null問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07SpringBoot中使用configtree讀取樹形文件目錄中的配置詳解
這篇文章主要介紹了SpringBoot中使用configtree讀取樹形文件目錄中的配置詳解,configtree通過spring.config.import?+?configtree:前綴的方式,加載以文件名為key、文件內(nèi)容為value的配置屬性,需要的朋友可以參考下2023-12-12Spring boot整合Springfox生成restful的在線api文檔
這篇文章主要為大家介紹了Spring boot整合Springfox生成restful在線api文檔,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-03-03Java常用數(shù)字工具類 大數(shù)乘法、加法、減法運算(2)
這篇文章主要為大家詳細介紹了Java常用數(shù)字工具類,大數(shù)乘法、加法、減法運算,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05SpringCloudStream原理和深入使用小結(jié)
Spring?Cloud?Stream是一個用于構(gòu)建與共享消息傳遞系統(tǒng)連接的高度可擴展的事件驅(qū)動型微服務(wù)的框架,本文給大家介紹SpringCloudStream原理和深入使用,感興趣的朋友跟隨小編一起看看吧2024-06-06