欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringSecurity+JWT實(shí)現(xiàn)登錄流程分析

 更新時(shí)間:2024年12月17日 11:32:05   作者:CRE_MO  
Spring Security 是一個(gè)功能強(qiáng)大且高度可定制的身份驗(yàn)證和訪問控制框架,它是為Java應(yīng)用程序設(shè)計(jì)的,特別是那些基于Spring的應(yīng)用程序,下面給大家介紹SpringSecurity+JWT實(shí)現(xiàn)登錄流程,感興趣的朋友一起看看吧

1. SpringSecurity介紹

Spring Security 是一個(gè)功能強(qiáng)大且高度可定制的身份驗(yàn)證和訪問控制框架。它是為Java應(yīng)用程序設(shè)計(jì)的,特別是那些基于Spring的應(yīng)用程序。Spring Security是一個(gè)社區(qū)驅(qū)動(dòng)的開源項(xiàng)目,它提供了全面的安全性解決方案,包括防止常見的安全漏洞如CSRF、點(diǎn)擊劫持、會話固定等。

以下是Spring Security的一些關(guān)鍵特性和概念:

  • 認(rèn)證(Authentication):Spring Security可以處理用戶的身份驗(yàn)證過程,即確認(rèn)用戶是否是他們聲稱的人。它可以使用多種機(jī)制來進(jìn)行身份驗(yàn)證,例如表單登錄、HTTP基本認(rèn)證、OAuth2、JWT等。
  • 授權(quán)(Authorization):一旦用戶通過了身份驗(yàn)證,Spring Security就會根據(jù)用戶的權(quán)限來決定他們可以訪問哪些資源。這可以通過定義角色、權(quán)限或更細(xì)粒度的訪問規(guī)則來實(shí)現(xiàn)。
  • 安全配置:Spring Security可以通過Java配置或XML配置來設(shè)置安全策略。通常推薦使用Java配置,因?yàn)樗c現(xiàn)代Spring應(yīng)用更為集成,并提供編譯時(shí)檢查。
  • 攔截URL模式:可以定義哪些URL需要特定的權(quán)限才能訪問,以及如何處理未認(rèn)證或未經(jīng)授權(quán)的請求。
  • 過濾器鏈:Spring Security利用了一組過濾器(Filter),這些過濾器在每次HTTP請求時(shí)被調(diào)用,以執(zhí)行各種安全相關(guān)的任務(wù)。開發(fā)者可以根據(jù)需要添加自定義過濾器。
  • 密碼編碼:為了安全存儲用戶密碼,Spring Security支持多種加密方式,如BCrypt、PBKDF2等。
  • 記住我(Remember-Me):允許系統(tǒng)在用戶關(guān)閉瀏覽器后仍然保持登錄狀態(tài),直到明確登出或cookie過期。
  • 注銷(Logout):提供了安全的退出機(jī)制,確保用戶的會話被正確地銷毀。
  • CSRF保護(hù):默認(rèn)啟用跨站請求偽造攻擊防護(hù),確保只有來自合法來源的請求才能修改服務(wù)器端的狀態(tài)。
  • Session管理:可以配置會話創(chuàng)建策略,例如只在需要時(shí)創(chuàng)建會話,或者限制同一時(shí)間內(nèi)的并發(fā)會話數(shù)量。
  • OAuth2和OpenID Connect支持:內(nèi)置對OAuth2客戶端和資源服務(wù)器的支持,方便集成第三方認(rèn)證服務(wù)。

使用Spring Security,開發(fā)者可以專注于業(yè)務(wù)邏輯的開發(fā),而將安全問題交給這個(gè)成熟可靠的框架來處理。同時(shí),由于其高度可擴(kuò)展性和靈活性,Spring Security也適合用于構(gòu)建復(fù)雜的安全需求。

2. 登錄流程

登錄API無需攔截,SpringSecurity直接放行。

/**
 * @description 認(rèn)證授權(quán)
 **/
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Api(tags = "認(rèn)證")
public class AuthController {
    private final AuthService authService;
    @PostMapping("/login")
    @ApiOperation("登錄")
    public ResponseEntity<Void> login(@RequestBody LoginRequest loginRequest) {
        String token = authService.createToken(loginRequest);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set(SecurityConstants.TOKEN_HEADER, token);
        return new ResponseEntity<>(httpHeaders, HttpStatus.OK);
    }
}

AuthService首先會校驗(yàn)用戶名與密碼,和用戶的角色,然后調(diào)用JwtTokenUtils創(chuàng)建token,然后以userId為key,token作為value存在Redis中。

@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AuthService {
    private final UserService userService;
    private final StringRedisTemplate stringRedisTemplate;
    private final CurrentUserUtils currentUserUtils;
    public String createToken(LoginRequest loginRequest) {
        User user = userService.find(loginRequest.getUsername());
        if (!userService.check(loginRequest.getPassword(), user.getPassword())) {
            throw new BadCredentialsException("The user name or password is not correct.");
        }
        JwtUser jwtUser = new JwtUser(user);
        if (!jwtUser.isEnabled()) {
            throw new BadCredentialsException("User is forbidden to login");
        }
        List<String> authorities = jwtUser.getAuthorities()
                .stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());
        String token = JwtTokenUtils.createToken(user.getUserName(), user.getId().toString(), authorities, loginRequest.getRememberMe());
        stringRedisTemplate.opsForValue().set(user.getId().toString(), token);
        return token;
    }
    public void removeToken() {
        stringRedisTemplate.delete(currentUserUtils.getCurrentUser().getId().toString());
    }
}

JwtTokenUtils負(fù)責(zé)創(chuàng)建token、解析token與獲取userId。

public class JwtTokenUtils {
    /**
     * 生成足夠的安全隨機(jī)密鑰,以適合符合規(guī)范的簽名
     */
    private static final byte[] API_KEY_SECRET_BYTES = DatatypeConverter.parseBase64Binary(SecurityConstants.JWT_SECRET_KEY);
    private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(API_KEY_SECRET_BYTES);
    public static String createToken(String username, String id, List<String> roles, boolean isRememberMe) {
        long expiration = isRememberMe ? SecurityConstants.EXPIRATION_REMEMBER : SecurityConstants.EXPIRATION;
        final Date createdDate = new Date();
        final Date expirationDate = new Date(createdDate.getTime() + expiration * 1000);
        String tokenPrefix = Jwts.builder()
                .setHeaderParam("type", SecurityConstants.TOKEN_TYPE)
                .signWith(SECRET_KEY, SignatureAlgorithm.HS256)
                .claim(SecurityConstants.ROLE_CLAIMS, String.join(",", roles))
                .setId(id)
                .setIssuer("SnailClimb")
                .setIssuedAt(createdDate)
                .setSubject(username)
                .setExpiration(expirationDate)
                .compact();
        return SecurityConstants.TOKEN_PREFIX + tokenPrefix; // 添加 token 前綴 "Bearer ";
    }
    // userId
    public static String getId(String token) {
        Claims claims = getClaims(token);
        return claims.getId();
    }
    // 得到 userName、token與 authorities
    public static UsernamePasswordAuthenticationToken getAuthentication(String token) {
        Claims claims = getClaims(token);
        List<SimpleGrantedAuthority> authorities = getAuthorities(claims);
        String userName = claims.getSubject();
        return new UsernamePasswordAuthenticationToken(userName, token, authorities);
    }
    private static List<SimpleGrantedAuthority> getAuthorities(Claims claims) {
        String role = (String) claims.get(SecurityConstants.ROLE_CLAIMS);
        return Arrays.stream(role.split(","))
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }
    private static Claims getClaims(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
    }
}

3. JWT認(rèn)證流程

// 啟用 SpringSecurity
@EnableWebSecurity
// 啟用 SpringSecurity 注解開發(fā)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private final StringRedisTemplate stringRedisTemplate;
    public SecurityConfiguration(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
    /**
     * 密碼編碼器
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors(withDefaults())
                // 禁用 CSRF
                .csrf().disable()
                .authorizeRequests()
                // 指定的接口直接放行
                // swagger
                .antMatchers(SecurityConstants.SWAGGER_WHITELIST).permitAll()
                .antMatchers(SecurityConstants.H2_CONSOLE).permitAll()
                .antMatchers(HttpMethod.POST, SecurityConstants.SYSTEM_WHITELIST).permitAll()
                // 其他的接口都需要認(rèn)證后才能請求
                .anyRequest().authenticated()
                .and()
                //添加自定義Filter
                .addFilter(new JwtAuthorizationFilter(authenticationManager(), stringRedisTemplate))
                // 不需要session(不創(chuàng)建會話)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 授權(quán)異常處理
                .exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint())
                .accessDeniedHandler(new JwtAccessDeniedHandler());
        // 防止H2 web 頁面的Frame 被攔截
        http.headers().frameOptions().disable();
    }
    /**
     * Cors配置優(yōu)化
     **/
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        org.springframework.web.cors.CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(singletonList("*"));
        // configuration.setAllowedOriginPatterns(singletonList("*"));
        configuration.setAllowedHeaders(singletonList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "DELETE", "PUT", "OPTIONS"));
        configuration.setExposedHeaders(singletonList(SecurityConstants.TOKEN_HEADER));
        configuration.setAllowCredentials(false);
        configuration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

自定義Filter

@Slf4j
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
    private final StringRedisTemplate stringRedisTemplate;
    // 不是 Bean, 需要手動(dòng)注入
    public JwtAuthorizationFilter(AuthenticationManager authenticationManager, StringRedisTemplate stringRedisTemplate) {
        super(authenticationManager);
        this.stringRedisTemplate = stringRedisTemplate;
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {
        String token = request.getHeader(SecurityConstants.TOKEN_HEADER);
        if (token == null || !token.startsWith(SecurityConstants.TOKEN_PREFIX)) {
            SecurityContextHolder.clearContext();
            chain.doFilter(request, response);
            return;
        }
        String tokenValue = token.replace(SecurityConstants.TOKEN_PREFIX, "");
        UsernamePasswordAuthenticationToken authentication = null;
        try {
            // token是否有效
            String previousToken = stringRedisTemplate.opsForValue().get(JwtTokenUtils.getId(tokenValue));
            if (!token.equals(previousToken)) {
                SecurityContextHolder.clearContext();
                chain.doFilter(request, response);
                return;
            }
            authentication = JwtTokenUtils.getAuthentication(tokenValue);
        } catch (JwtException e) {
            logger.error("Invalid jwt : " + e.getMessage());
        }
        // 將userName, token, authorities保存在Context中
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);
    }
}

SecurityContextHolder是基于ThreadLocal實(shí)現(xiàn)的,可以實(shí)現(xiàn)不同線程之間的隔離。

public class SecurityContextHolder {
    public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
    public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
    public static final String MODE_GLOBAL = "MODE_GLOBAL";
    public static final String SYSTEM_PROPERTY = "spring.security.strategy";
    private static String strategyName = System.getProperty("spring.security.strategy");
    private static SecurityContextHolderStrategy strategy;
    private static int initializeCount = 0;
    public SecurityContextHolder() {
    }
    private static void initialize() {
        if (!StringUtils.hasText(strategyName)) {
            strategyName = "MODE_THREADLOCAL";
        }
        if (strategyName.equals("MODE_THREADLOCAL")) {
            strategy = new ThreadLocalSecurityContextHolderStrategy();
        } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
            strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
        } else if (strategyName.equals("MODE_GLOBAL")) {
            strategy = new GlobalSecurityContextHolderStrategy();
        } else {
            try {
                Class<?> clazz = Class.forName(strategyName);
                Constructor<?> customStrategy = clazz.getConstructor();
                strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
            } catch (Exception var2) {
                Exception ex = var2;
                ReflectionUtils.handleReflectionException(ex);
            }
        }
        ++initializeCount;
    }
}

4. 全局異常處理器

public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    /**
     * 當(dāng)用戶嘗試訪問需要權(quán)限才能的REST資源而不提供Token或者Token錯(cuò)誤或者過期時(shí),
     * 將調(diào)用此方法發(fā)送401響應(yīng)以及錯(cuò)誤信息
     */
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
    }
}
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    /**
     * 當(dāng)用戶嘗試訪問需要權(quán)限才能的REST資源而權(quán)限不足的時(shí)候,
     * 將調(diào)用此方法發(fā)送403響應(yīng)以及錯(cuò)誤信息
     */
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        accessDeniedException = new AccessDeniedException("Sorry you don not enough permissions to access it!");
        response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
    }
}

5. 注銷流程

刪除Redis中保存的token。

@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AuthService {
    private final UserService userService;
    private final StringRedisTemplate stringRedisTemplate;
    private final CurrentUserUtils currentUserUtils;
    public String createToken(LoginRequest loginRequest) {
        User user = userService.find(loginRequest.getUsername());
        if (!userService.check(loginRequest.getPassword(), user.getPassword())) {
            throw new BadCredentialsException("The user name or password is not correct.");
        }
        JwtUser jwtUser = new JwtUser(user);
        if (!jwtUser.isEnabled()) {
            throw new BadCredentialsException("User is forbidden to login");
        }
        List<String> authorities = jwtUser.getAuthorities()
                .stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());
        String token = JwtTokenUtils.createToken(user.getUserName(), user.getId().toString(), authorities, loginRequest.getRememberMe());
        stringRedisTemplate.opsForValue().set(user.getId().toString(), token);
        return token;
    }
    public void removeToken() {
        stringRedisTemplate.delete(currentUserUtils.getCurrentUser().getId().toString());
    }
}
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CurrentUserUtils {
    private final UserService userService;
    public User getCurrentUser() {
        return userService.find(getCurrentUserName());
    }
    private  String getCurrentUserName() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() != null) {
            return (String) authentication.getPrincipal();
        }
        return null;
    }
}

6. 權(quán)限管理

基于@PreAuthorize實(shí)現(xiàn)權(quán)限管理

@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@RequestMapping("/users")
@Api(tags = "用戶")
public class UserController {
    private final UserService userService;
    @GetMapping
    // 有任意角色的權(quán)限都可以訪問
    @PreAuthorize("hasAnyRole('ROLE_USER','ROLE_MANAGER','ROLE_ADMIN')")
    @ApiOperation("獲取所有用戶的信息(分頁)")
    public ResponseEntity<Page<UserRepresentation>> getAllUser(@RequestParam(value = "pageNum", defaultValue = "0") int pageNum, @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        System.out.println("auth信息: " + authentication.getPrincipal().toString() + " 鑒權(quán)" + authentication.getAuthorities().toString());
        System.out.println("***********");
        Page<UserRepresentation> allUser = userService.getAll(pageNum, pageSize);
        return ResponseEntity.ok().body(allUser);
    }
    @PutMapping
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    @ApiOperation("更新用戶")
    public ResponseEntity<Void> update(@RequestBody @Valid UserUpdateRequest userUpdateRequest) {
        userService.update(userUpdateRequest);
        return ResponseEntity.ok().build();
    }
    @DeleteMapping
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    @ApiOperation("根據(jù)用戶名刪除用戶")
    public ResponseEntity<Void> deleteUserByUserName(@RequestParam("username") String username) {
        userService.delete(username);
        return ResponseEntity.ok().build();
    }
}

到此這篇關(guān)于SpringSecurity+JWT實(shí)現(xiàn)登錄流程分析的文章就介紹到這了,更多相關(guān)SpringSecurity JWT登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot自動(dòng)配置的8個(gè)技巧分享

    SpringBoot自動(dòng)配置的8個(gè)技巧分享

    在 SpringBoot 2.x中,一個(gè)很核心的功能是自動(dòng)配置機(jī)制,這篇文章主要為大家詳細(xì)介紹了Spring Boot 2.x 實(shí)現(xiàn)自動(dòng)配置的8個(gè)技巧,希望對大家有所幫助
    2025-01-01
  • SpringBoot中的文件上傳與下載詳解

    SpringBoot中的文件上傳與下載詳解

    這篇文章主要介紹了SpringBoot中的文件上傳與下載詳解,springboot是spring家族中的一個(gè)全新框架,用來簡化spring程序的創(chuàng)建和開發(fā)過程,本文我們就一起來看看上傳與下載的操作,需要的朋友可以參考下
    2023-08-08
  • springboot 配置日志 打印不出來sql的解決方法

    springboot 配置日志 打印不出來sql的解決方法

    這篇文章主要介紹了springboot 配置日志 打印不出來sql的解決方法,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下
    2020-11-11
  • Spring中的@PathVariable注解詳細(xì)解析

    Spring中的@PathVariable注解詳細(xì)解析

    這篇文章主要介紹了Spring中的@PathVariable注解詳細(xì)解析,@PathVariable 是 Spring 框架中的一個(gè)注解,用于將 URL 中的變量綁定到方法的參數(shù)上,它通常用于處理 RESTful 風(fēng)格的請求,從 URL 中提取參數(shù)值,并將其傳遞給方法進(jìn)行處理,需要的朋友可以參考下
    2024-01-01
  • mybatis一級緩存和二級緩存的區(qū)別及說明

    mybatis一級緩存和二級緩存的區(qū)別及說明

    這篇文章主要介紹了mybatis一級緩存和二級緩存的區(qū)別及說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • 圖文詳解SpringBoot中Log日志的集成

    圖文詳解SpringBoot中Log日志的集成

    這篇文章主要給大家介紹了關(guān)于SpringBoot中Log日志的集成的相關(guān)資料,文中通過實(shí)例代碼以及圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2021-12-12
  • Java設(shè)計(jì)模式之策略模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java設(shè)計(jì)模式之策略模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    策略模式是對算法的封裝,把一系列的算法分別封裝到對應(yīng)的類中,并且這些類實(shí)現(xiàn)相同的接口,相互之間可以替換。接下來通過本文給大家分享Java設(shè)計(jì)模式之策略模式,感興趣的朋友一起看看吧
    2017-08-08
  • Java中的包(Package)與導(dǎo)入(Import)示例詳解

    Java中的包(Package)與導(dǎo)入(Import)示例詳解

    這篇文章主要詳細(xì)介紹了Java中的包(Package)和導(dǎo)入(Import)概念,包括包的定義、作用、JDK中主要的包、導(dǎo)入的目的與用法、特殊情況的導(dǎo)入、靜態(tài)導(dǎo)入、包的訪問權(quán)限和命名規(guī)范,文章通過豐富的解釋和代碼示例,幫助讀者深入理解這些概念的實(shí)際應(yīng)用,需要的朋友可以參考下
    2024-11-11
  • Java匿名類和匿名函數(shù)的概念和寫法

    Java匿名類和匿名函數(shù)的概念和寫法

    匿名函數(shù)寫法和匿名類寫法的前提必須基于函數(shù)式接口匿名函數(shù)寫法和匿名類寫法其本質(zhì)是同一個(gè)東西,只是簡化寫法不同使用Lambda表達(dá)式簡寫匿名函數(shù)時(shí),可以同時(shí)省略實(shí)現(xiàn)類名、函數(shù)名,這篇文章主要介紹了Java匿名類和匿名函數(shù)的概念和寫法,需要的朋友可以參考下
    2023-06-06
  • 關(guān)于JDK+Tomcat+eclipse+MyEclipse的配置方法,看這篇夠了

    關(guān)于JDK+Tomcat+eclipse+MyEclipse的配置方法,看這篇夠了

    關(guān)于JDK+Tomcat+eclipse+MyEclipse的配置問題,很多朋友都搞不太明白,網(wǎng)上一搜配置方法多種哪種最精簡呢,今天小編給大家分享一篇文章幫助大家快速掌握J(rèn)DK Tomcat eclipse MyEclipse配置技巧,需要的朋友參考下吧
    2021-06-06

最新評論