SpringSecurity?鑒權(quán)與授權(quán)的具體使用
前言
在現(xiàn)代 Web 開發(fā)中,前后端分離架構(gòu)已經(jīng)成為主流。后端專注于提供 RESTful API,而前端通過 AJAX 請求與后端交互。在這種架構(gòu)下,如何對用戶進(jìn)行 認(rèn)證(Authentication) 和 授權(quán)(Authorization) 成為了系統(tǒng)設(shè)計(jì)中的核心問題。
Spring Security 是 Spring 框架中用于構(gòu)建安全系統(tǒng)的模塊,它不僅提供了強(qiáng)大的安全機(jī)制,還支持靈活的自定義配置。本文將圍繞 鑒權(quán)失敗和成功時(shí)的行為、需要攔截的路徑配置、以及具體的代碼實(shí)現(xiàn)方式 展開講解,并結(jié)合多個(gè)實(shí)際例子,幫助你深入理解 Spring Security 在前后端分離項(xiàng)目中的使用。
本文將從以下三個(gè)方面詳細(xì)展開:
- 基礎(chǔ)概念:包括認(rèn)證、授權(quán)、關(guān)鍵組件及其作用;
- 配置流程:從登錄請求到權(quán)限控制的完整流程;
- 實(shí)戰(zhàn)示例:JWT 認(rèn)證、異常處理、多種路徑保護(hù)等具體實(shí)現(xiàn);
無論你是初學(xué)者還是有一定經(jīng)驗(yàn)的開發(fā)者,這篇文章都將為你提供一套完整的解決方案。
一、基礎(chǔ)概念詳解
Spring Security 的核心在于 認(rèn)證(Authentication) 和 授權(quán)(Authorization)。
1.SecurityFilterChain(安全過濾器鏈)
- 作用:定義 HTTP 請求的安全策略。
- 位置:通常在配置類中通過
@Bean
注解創(chuàng)建。 - 示例:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .formLogin(withDefaults()) .httpBasic(withDefaults()); return http.build(); }
可以定義多個(gè) SecurityFilterChain 來處理不同路徑的權(quán)限策略。
2.UserDetailsService(用戶詳情服務(wù))
- 作用:負(fù)責(zé)加載用戶信息(用戶名、密碼、權(quán)限)。
- 實(shí)現(xiàn)方式:
- 內(nèi)存方式(適合測試):
@Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password(passwordEncoder().encode("123456")) .roles("USER") .build(); return new InMemoryUserDetailsManager(user); }
- 數(shù)據(jù)庫方式(推薦生產(chǎn)環(huán)境):
@Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("用戶不存在")); return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), getAuthorities(user.getRoles())); } private Collection<? extends GrantedAuthority> getAuthorities(Collection<Role> roles) { return roles.stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())) .collect(Collectors.toList()); } }
3.PasswordEncoder(密碼編碼器)
- 作用:加密存儲密碼,并驗(yàn)證密碼是否匹配。
- 常用實(shí)現(xiàn):
BCryptPasswordEncoder
(推薦)NoOpPasswordEncoder
(不加密,僅用于開發(fā)階段)
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
4.JwtRequestFilter(自定義 JWT 過濾器)
- 作用:攔截每個(gè)請求,解析 Token 并設(shè)置當(dāng)前用戶認(rèn)證信息。
- 實(shí)現(xiàn)方式:
@Component public class JwtRequestFilter extends OncePerRequestFilter { @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = extractToken(request); if (token != null && jwtTokenUtil.validateToken(token)) { String username = jwtTokenUtil.getUsernameFromToken(token); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); } private String extractToken(HttpServletRequest request) { String header = request.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { return header.substring(7); } return null; } }
5.AuthenticationEntryPoint(鑒權(quán)失敗處理器)
- 作用:當(dāng)用戶未認(rèn)證訪問受保護(hù)資源時(shí)觸發(fā)該處理器。
- 實(shí)現(xiàn)方式:
@Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"認(rèn)證失敗,請重新登錄\"}"); } }
6.AccessDeniedHandler(權(quán)限不足處理器)
- 作用:當(dāng)用戶已認(rèn)證但沒有訪問權(quán)限時(shí)觸發(fā)。
- 實(shí)現(xiàn)方式:
@Component public class JwtAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.getWriter().write("{\"error\": \"Forbidden\", \"message\": \"你沒有權(quán)限訪問該資源\"}"); } }
二、配置流程詳解
成功時(shí)的流程:
- 用戶發(fā)送登錄請求 →
/api/auth/login
- 后端驗(yàn)證用戶名密碼,生成 JWT Token 并返回給前端
- 前端保存 Token(如 localStorage)
- 后續(xù)請求攜帶 Token(放在 Header 中)
- 后端通過自定義 JWT 過濾器解析 Token 并設(shè)置認(rèn)證信息
- 用戶訪問受保護(hù)資源成功
失敗時(shí)的流程:
- Token 缺失或格式錯(cuò)誤 → 返回
401 Unauthorized
- Token 已過期或簽名無效 → 返回
401 Unauthorized
- 用戶沒有權(quán)限訪問某資源 → 返回
403 Forbidden
三、實(shí)戰(zhàn)示例詳解
示例 1:JWT Token 工具類(完整版)
@Component public class JwtTokenUtil { private static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60; // 5小時(shí) private String secret = "your-secret-key-here"; public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } private Claims getAllClaimsFromToken(String token) { return Jwts.parserBuilder() .setSigningKey(secret) .build() .parseClaimsJws(token) .getBody(); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return doGenerateToken(claims, userDetails.getUsername()); } private String doGenerateToken(Map<String, Object> claims, String subject) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public Boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } }
示例 2:登錄接口
@RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserDetailsService userDetailsService; @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) throws Exception { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()) ); } catch (AuthenticationException e) { throw new Exception("用戶名或密碼錯(cuò)誤"); } final UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername()); final String token = jwtTokenUtil.generateToken(userDetails); return ResponseEntity.ok().header("Authorization", "Bearer " + token).build(); } }
示例 3:多種路徑保護(hù)方式
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .cors(cors -> cors.configurationSource(corsConfigurationSource())) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint(authenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/public/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated() ); return http.build(); }
結(jié)語
本文從 Spring Security 的基礎(chǔ)概念出發(fā),詳細(xì)介紹了認(rèn)證與授權(quán)的核心組件,并結(jié)合多個(gè)實(shí)戰(zhàn)示例展示了 JWT 的集成、路徑保護(hù)、異常處理等常見場景的實(shí)現(xiàn)方式。
到此這篇關(guān)于SpringSecurity 鑒權(quán)與授權(quán)的具體使用的文章就介紹到這了,更多相關(guān)SpringSecurity 鑒權(quán)與授權(quán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot整合SpringSecurity實(shí)現(xiàn)登錄認(rèn)證和鑒權(quán)全過程
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringBoot整合SpringSecurityOauth2實(shí)現(xiàn)鑒權(quán)動(dòng)態(tài)權(quán)限問題
- SpringBoot集成SpringSecurity和JWT做登陸鑒權(quán)的實(shí)現(xiàn)
- SpringSecurity動(dòng)態(tài)加載用戶角色權(quán)限實(shí)現(xiàn)登錄及鑒權(quán)功能
- springboot+jwt+springSecurity微信小程序授權(quán)登錄問題
- SpringSecurity實(shí)現(xiàn)權(quán)限認(rèn)證與授權(quán)的使用示例
- SpringSecurity進(jìn)行認(rèn)證與授權(quán)的示例代碼
- springSecurity用戶認(rèn)證和授權(quán)的實(shí)現(xiàn)
- 深入淺析springsecurity入門登錄授權(quán)
- mall整合SpringSecurity及JWT實(shí)現(xiàn)認(rèn)證授權(quán)實(shí)戰(zhàn)
- SpringSecurity頁面授權(quán)與登錄驗(yàn)證實(shí)現(xiàn)(內(nèi)存取值與數(shù)據(jù)庫取值)
相關(guān)文章
SpringBoot整合達(dá)夢數(shù)據(jù)庫的教程詳解
這篇文章主要給大家介紹了SpringBoot整合達(dá)夢數(shù)據(jù)庫的詳細(xì)教程,文章中有詳細(xì)的圖片介紹和代碼示例供大家參考,具有一定的參考價(jià)值,需要的朋友可以參考下2023-08-08java提取json中某個(gè)數(shù)組的所有值方法
下面小編就為大家分享一篇java提取json中某個(gè)數(shù)組的所有值方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03解析java稀疏數(shù)組如何幫助我們節(jié)省內(nèi)存提升性能
這篇文章主要為大家介紹了java稀疏數(shù)組如何幫助我們節(jié)省內(nèi)存提升性能解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11