Spring Security 單點登錄與自動登錄機制的實現(xiàn)原理
在現(xiàn)代企業(yè)級應用中,用戶需要訪問多個相關但獨立的系統(tǒng)。傳統(tǒng)的每次訪問都需要重新登錄的方式不僅用戶體驗差,而且安全性也難以保障。本文將深入探討基于Spring Security的單點登錄(SSO)和自動登錄機制的實現(xiàn)原理。
一、核心概念解析
1.1 單點登錄(SSO)
單點登錄是指用戶只需要登錄一次,就可以訪問所有相互信任的應用系統(tǒng)。
1.2 自動登錄(Remember Me)
自動登錄是指用戶在一定時間內(nèi)無需重復輸入用戶名密碼即可自動完成身份認證。
二、代碼分析
讓我們先分析一下提供的代碼片段:
// 1. 手動查詢用戶
SysUser sysUser = userService.selectUserByUserName(username);
if (sysUser == null) {
throw new UsernameNotFoundException("用戶不存在");
}
// 3. 查詢權(quán)限
Set<String> permissions = sysPermissionService.getMenuPermission(sysUser);
// 4. 構(gòu)造LoginUser對象
LoginUser loginUser = new LoginUser(sysUser.getUserId(),sysUser.getDeptId(),sysUser, permissions);
// 4. 構(gòu)造已認證的Authentication對象
authentication = new UsernamePasswordAuthenticationToken(
loginUser, // principal - 這里傳遞的是完整的LoginUser對象
null, // credentials
loginUser.getAuthorities() // authorities
);
// 5. 設置到Security上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
Long userId = SecurityUtils.getUserId();這段代碼展示了手動構(gòu)建認證信息的核心流程。
三、單點登錄實現(xiàn)方案
3.1 基于JWT的SSO實現(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認證過濾器
@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;
}
}四、自動登錄機制實現(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小時
.userDetailsService(userDetailsService);
}
}4.2 持久化Token存儲
@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);
}
}五、完整登錄服務實現(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. 驗證碼校驗
validateCaptcha(code, uuid);
// 2. 用戶認證
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)
);
// 3. 認證成功后生成JWT Token
SecurityContextHolder.getContext().setAuthentication(authentication);
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
return tokenProvider.generateToken(loginUser);
}
/**
* 自動登錄處理
*/
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("自動登錄失敗");
}
private void validateCaptcha(String code, String uuid) {
// 驗證碼校驗邏輯
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String captcha = redisCache.getCacheObject(verifyKey);
redisCache.deleteObject(verifyKey);
if (captcha == null || !code.equalsIgnoreCase(captcha)) {
throw new CaptchaException("驗證碼錯誤");
}
}
}六、安全工具類
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();
}
}七、最佳實踐建議
7.1 安全性考慮
- Token過期時間:合理設置JWT過期時間
- Token刷新機制:實現(xiàn)Token刷新避免頻繁登錄
- HTTPS傳輸:確保Token在傳輸過程中的安全
7.2 性能優(yōu)化
- 緩存機制:對用戶權(quán)限信息進行緩存
- 異步處理:將非關鍵業(yè)務異步處理
- 數(shù)據(jù)庫優(yōu)化:對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("登錄耗時: {}ms", endTime - startTime);
}
}
}八、總結(jié)
通過本文的介紹,我們了解了:
- 單點登錄的核心原理:基于JWT實現(xiàn)跨系統(tǒng)認證
- 自動登錄的實現(xiàn)機制:RememberMe和持久化Token存儲
- Spring Security集成:如何與現(xiàn)有安全框架整合
- 最佳實踐:安全性和性能方面的考慮
在實際項目中,需要根據(jù)業(yè)務需求選擇合適的方案,并注意安全性和性能的平衡。單點登錄和自動登錄機制的合理運用,能夠顯著提升用戶體驗和系統(tǒng)安全性。
到此這篇關于Spring Security 單點登錄與自動登錄機制的實現(xiàn)原理的文章就介紹到這了,更多相關Spring Security 單點登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- SpringSecurity自動登錄流程與實現(xiàn)詳解
- SpringBoot 配合 SpringSecurity 實現(xiàn)自動登錄功能的代碼
- Spring Security 自動踢掉前一個登錄用戶的實現(xiàn)代碼
- Spring security實現(xiàn)記住我下次自動登錄功能過程詳解
- Spring Security實現(xiàn)兩周內(nèi)自動登錄"記住我"功能
- 詳解使用Spring Security進行自動登錄驗證
- Spring?boot?security權(quán)限管理集成cas單點登錄功能的實現(xiàn)
- Spring Security基于JWT實現(xiàn)SSO單點登錄詳解
- 使用Spring Security OAuth2實現(xiàn)單點登錄
相關文章
java求數(shù)組元素重復次數(shù)和java字符串比較大小示例
這篇文章主要介紹了java求數(shù)組元素重復次數(shù)和java字符串比較大小示例,需要的朋友可以參考下2014-04-04
SpringSecurity中@PermitAll與@PreAuthorize的實現(xiàn)
@PermitAll和@PreAuthorize都是處理安全性的強大工具,本文主要介紹了SpringSecurity中@PermitAll與@PreAuthorize的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-07-07
Java實現(xiàn)簡易版聯(lián)網(wǎng)坦克對戰(zhàn)小游戲(附源碼)
這篇文章主要給大家介紹了關于Java實現(xiàn)簡易版聯(lián)網(wǎng)坦克對戰(zhàn)小游戲的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用java具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-04-04
java.imageIo給圖片添加水印的實現(xiàn)代碼
最近項目在做一個商城項目, 項目上的圖片要添加水?、?添加圖片水印;②:添加文字水印;一下提供下個方法,希望大家可以用得著2013-07-07

