Spring Security 單點(diǎn)登錄與自動(dòng)登錄機(jī)制的實(shí)現(xiàn)原理
在現(xiàn)代企業(yè)級(jí)應(yīng)用中,用戶需要訪問(wèn)多個(gè)相關(guān)但獨(dú)立的系統(tǒng)。傳統(tǒng)的每次訪問(wèn)都需要重新登錄的方式不僅用戶體驗(yàn)差,而且安全性也難以保障。本文將深入探討基于Spring Security的單點(diǎn)登錄(SSO)和自動(dòng)登錄機(jī)制的實(shí)現(xiàn)原理。
一、核心概念解析
1.1 單點(diǎn)登錄(SSO)
單點(diǎn)登錄是指用戶只需要登錄一次,就可以訪問(wèn)所有相互信任的應(yīng)用系統(tǒng)。
1.2 自動(dòng)登錄(Remember Me)
自動(dòng)登錄是指用戶在一定時(shí)間內(nèi)無(wú)需重復(fù)輸入用戶名密碼即可自動(dòng)完成身份認(rèn)證。
二、代碼分析
讓我們先分析一下提供的代碼片段:
// 1. 手動(dòng)查詢用戶 SysUser sysUser = userService.selectUserByUserName(username); if (sysUser == null) { throw new UsernameNotFoundException("用戶不存在"); } // 3. 查詢權(quán)限 Set<String> permissions = sysPermissionService.getMenuPermission(sysUser); // 4. 構(gòu)造LoginUser對(duì)象 LoginUser loginUser = new LoginUser(sysUser.getUserId(),sysUser.getDeptId(),sysUser, permissions); // 4. 構(gòu)造已認(rèn)證的Authentication對(duì)象 authentication = new UsernamePasswordAuthenticationToken( loginUser, // principal - 這里傳遞的是完整的LoginUser對(duì)象 null, // credentials loginUser.getAuthorities() // authorities ); // 5. 設(shè)置到Security上下文 SecurityContextHolder.getContext().setAuthentication(authentication); Long userId = SecurityUtils.getUserId();
這段代碼展示了手動(dòng)構(gòu)建認(rèn)證信息的核心流程。
三、單點(diǎn)登錄實(shí)現(xiàn)方案
3.1 基于JWT的SSO實(shí)現(xiàn)
@Component public class JwtTokenProvider { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String generateToken(LoginUser loginUser) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration); return Jwts.builder() .setSubject(loginUser.getUsername()) .claim("userId", loginUser.getUserId()) .claim("permissions", loginUser.getPermissions()) .setIssuedAt(new Date()) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String validateTokenAndGetUserId(String token) { Claims claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); return claims.get("userId", String.class); } }
3.2 SSO認(rèn)證過(guò)濾器
@Component public class SsoAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtTokenProvider tokenProvider; @Autowired private UserService userService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getJwtFromRequest(request); if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { try { String userId = tokenProvider.validateTokenAndGetUserId(token); SysUser sysUser = userService.selectUserById(userId); if (sysUser != null) { Set<String> permissions = sysPermissionService.getMenuPermission(sysUser); LoginUser loginUser = new LoginUser(sysUser.getUserId(), sysUser.getDeptId(), sysUser, permissions); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( loginUser, null, loginUser.getAuthorities() ); authentication.setDetails( new WebAuthenticationDetailsSource().buildDetails(request) ); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception ex) { logger.error("Could not set user authentication in security context", ex); } } filterChain.doFilter(request, response); } private String getJwtFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }
四、自動(dòng)登錄機(jī)制實(shí)現(xiàn)
4.1 RememberMe配置
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private PersistentTokenRepository persistentTokenRepository; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/login", "/public/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .and() .rememberMe() .rememberMeParameter("remember-me") .tokenRepository(persistentTokenRepository) .tokenValiditySeconds(86400) // 24小時(shí) .userDetailsService(userDetailsService); } }
4.2 持久化Token存儲(chǔ)
@Component public class PersistentTokenRepositoryImpl implements PersistentTokenRepository { @Autowired private RememberMeTokenMapper rememberMeTokenMapper; @Override public void createNewToken(PersistentRememberMeToken token) { RememberMeToken entity = new RememberMeToken(); entity.setSeries(token.getSeries()); entity.setUsername(token.getUsername()); entity.setToken(token.getTokenValue()); entity.setLastUsed(token.getDate()); rememberMeTokenMapper.insert(entity); } @Override public void updateToken(String series, String tokenValue, Date lastUsed) { RememberMeToken entity = new RememberMeToken(); entity.setSeries(series); entity.setToken(tokenValue); entity.setLastUsed(lastUsed); rememberMeTokenMapper.updateByPrimaryKey(entity); } @Override public PersistentRememberMeToken getTokenForSeries(String seriesId) { RememberMeToken entity = rememberMeTokenMapper.selectByPrimaryKey(seriesId); if (entity != null) { return new PersistentRememberMeToken( entity.getUsername(), entity.getSeries(), entity.getToken(), entity.getLastUsed() ); } return null; } @Override public void removeUserTokens(String username) { rememberMeTokenMapper.deleteByUsername(username); } }
五、完整登錄服務(wù)實(shí)現(xiàn)
@Service public class SysLoginService { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenProvider tokenProvider; @Autowired private UserService userService; @Autowired private SysPermissionService sysPermissionService; /** * 用戶登錄 */ public String login(String username, String password, String code, String uuid) { // 1. 驗(yàn)證碼校驗(yàn) validateCaptcha(code, uuid); // 2. 用戶認(rèn)證 Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(username, password) ); // 3. 認(rèn)證成功后生成JWT Token SecurityContextHolder.getContext().setAuthentication(authentication); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); return tokenProvider.generateToken(loginUser); } /** * 自動(dòng)登錄處理 */ public String autoLogin(String token) { if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { String userId = tokenProvider.validateTokenAndGetUserId(token); SysUser sysUser = userService.selectUserById(userId); if (sysUser != null) { Set<String> permissions = sysPermissionService.getMenuPermission(sysUser); LoginUser loginUser = new LoginUser(sysUser.getUserId(), sysUser.getDeptId(), sysUser, permissions); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( loginUser, null, loginUser.getAuthorities() ); SecurityContextHolder.getContext().setAuthentication(authentication); // 生成新的token return tokenProvider.generateToken(loginUser); } } throw new AuthenticationException("自動(dòng)登錄失敗"); } private void validateCaptcha(String code, String uuid) { // 驗(yàn)證碼校驗(yàn)邏輯 String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; String captcha = redisCache.getCacheObject(verifyKey); redisCache.deleteObject(verifyKey); if (captcha == null || !code.equalsIgnoreCase(captcha)) { throw new CaptchaException("驗(yàn)證碼錯(cuò)誤"); } } }
六、安全工具類(lèi)
public class SecurityUtils { /** * 獲取用戶ID */ public static Long getUserId() { try { return getLoginUser().getUserId(); } catch (Exception e) { throw new CustomException("獲取用戶ID異常", HttpStatus.UNAUTHORIZED); } } /** * 獲取登錄用戶信息 */ public static LoginUser getLoginUser() { try { return (LoginUser) getAuthentication().getPrincipal(); } catch (Exception e) { throw new CustomException("獲取用戶信息異常", HttpStatus.UNAUTHORIZED); } } /** * 獲取Authentication */ public static Authentication getAuthentication() { return SecurityContextHolder.getContext().getAuthentication(); } }
七、最佳實(shí)踐建議
7.1 安全性考慮
- Token過(guò)期時(shí)間:合理設(shè)置JWT過(guò)期時(shí)間
- Token刷新機(jī)制:實(shí)現(xiàn)Token刷新避免頻繁登錄
- HTTPS傳輸:確保Token在傳輸過(guò)程中的安全
7.2 性能優(yōu)化
- 緩存機(jī)制:對(duì)用戶權(quán)限信息進(jìn)行緩存
- 異步處理:將非關(guān)鍵業(yè)務(wù)異步處理
- 數(shù)據(jù)庫(kù)優(yōu)化:對(duì)RememberMe表建立合適的索引
7.3 監(jiān)控和日志
@Component public class LoginLogAspect { @Around("execution(* com.example.service.SysLoginService.login(..))") public Object logLogin(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = null; try { result = joinPoint.proceed(); // 記錄成功日志 logLoginSuccess(joinPoint.getArgs()); return result; } catch (Exception e) { // 記錄失敗日志 logLoginFailure(joinPoint.getArgs(), e); throw e; } finally { long endTime = System.currentTimeMillis(); logger.info("登錄耗時(shí): {}ms", endTime - startTime); } } }
八、總結(jié)
通過(guò)本文的介紹,我們了解了:
- 單點(diǎn)登錄的核心原理:基于JWT實(shí)現(xiàn)跨系統(tǒng)認(rèn)證
- 自動(dòng)登錄的實(shí)現(xiàn)機(jī)制:RememberMe和持久化Token存儲(chǔ)
- Spring Security集成:如何與現(xiàn)有安全框架整合
- 最佳實(shí)踐:安全性和性能方面的考慮
在實(shí)際項(xiàng)目中,需要根據(jù)業(yè)務(wù)需求選擇合適的方案,并注意安全性和性能的平衡。單點(diǎn)登錄和自動(dòng)登錄機(jī)制的合理運(yùn)用,能夠顯著提升用戶體驗(yàn)和系統(tǒng)安全性。
到此這篇關(guān)于Spring Security 單點(diǎn)登錄與自動(dòng)登錄機(jī)制的實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Spring Security 單點(diǎn)登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity自動(dòng)登錄流程與實(shí)現(xiàn)詳解
- SpringBoot 配合 SpringSecurity 實(shí)現(xiàn)自動(dòng)登錄功能的代碼
- Spring Security 自動(dòng)踢掉前一個(gè)登錄用戶的實(shí)現(xiàn)代碼
- Spring security實(shí)現(xiàn)記住我下次自動(dòng)登錄功能過(guò)程詳解
- Spring Security實(shí)現(xiàn)兩周內(nèi)自動(dòng)登錄"記住我"功能
- 詳解使用Spring Security進(jìn)行自動(dòng)登錄驗(yàn)證
- Spring?boot?security權(quán)限管理集成cas單點(diǎn)登錄功能的實(shí)現(xiàn)
- Spring Security基于JWT實(shí)現(xiàn)SSO單點(diǎn)登錄詳解
- 使用Spring Security OAuth2實(shí)現(xiàn)單點(diǎn)登錄
相關(guān)文章
java求數(shù)組元素重復(fù)次數(shù)和java字符串比較大小示例
這篇文章主要介紹了java求數(shù)組元素重復(fù)次數(shù)和java字符串比較大小示例,需要的朋友可以參考下2014-04-04輕松學(xué)會(huì)使用JavaMail?API發(fā)送郵件
想要輕松學(xué)會(huì)使用JavaMail?API發(fā)送郵件嗎?本指南將帶你快速掌握這一技能,讓你能夠輕松發(fā)送電子郵件,無(wú)論是個(gè)人還是工作需求,跟著我們的步驟,很快你就可以在Java應(yīng)用程序中自如地處理郵件通信了!2023-12-12SpringSecurity中@PermitAll與@PreAuthorize的實(shí)現(xiàn)
@PermitAll和@PreAuthorize都是處理安全性的強(qiáng)大工具,本文主要介紹了SpringSecurity中@PermitAll與@PreAuthorize的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07Java實(shí)現(xiàn)簡(jiǎn)易版聯(lián)網(wǎng)坦克對(duì)戰(zhàn)小游戲(附源碼)
這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)簡(jiǎn)易版聯(lián)網(wǎng)坦克對(duì)戰(zhàn)小游戲的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04java.imageIo給圖片添加水印的實(shí)現(xiàn)代碼
最近項(xiàng)目在做一個(gè)商城項(xiàng)目, 項(xiàng)目上的圖片要添加水?、?添加圖片水印;②:添加文字水印;一下提供下個(gè)方法,希望大家可以用得著2013-07-07一文帶你快速學(xué)會(huì)JDBC及獲取連接的五種方式
JDBC(Java Database Connectivity)是一個(gè)獨(dú)立于特定數(shù)據(jù)庫(kù)管理系統(tǒng)、通用的SQL數(shù)據(jù)庫(kù)存取和操作的公共接口,下面這篇文章主要給大家介紹了關(guān)于如何通過(guò)一文帶你快速學(xué)會(huì)JDBC及獲取連接的五種方式,需要的朋友可以參考下2022-09-09