最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot Security+JWT簡(jiǎn)單搭建的實(shí)現(xiàn)示例

 更新時(shí)間:2025年09月02日 10:59:30   作者:現(xiàn)在沒有牛仔了  
本文介紹在Spring Boot 2.6.13項(xiàng)目中集成Security與JWT實(shí)現(xiàn)認(rèn)證鑒權(quán)的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

SpringBoot SecuritySpring官方提供的一個(gè)安全框架,他的核心功能是對(duì)系統(tǒng)用戶進(jìn)行認(rèn)證和鑒權(quán),也經(jīng)常在項(xiàng)目中被使用到,本文不介紹其太過深入的內(nèi)容,只介紹如何實(shí)現(xiàn)并完成認(rèn)證和鑒權(quán)的測(cè)試。主要分三步來實(shí)現(xiàn):

  1. 配置JWT
  2. 配置Security
  3. 編寫測(cè)試相關(guān)代碼

首先創(chuàng)建一個(gè)springboot項(xiàng)目,我的版本是2.6.13,依然是java8,整合Security+JWT需要用到的Maven依賴如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jwt-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

配置JWT

先在yml配置文件中添加jwt相關(guān)配置

jwt:
    expiration: 3600000 //token過期時(shí)間,1個(gè)小時(shí)
    tokenHeader: Authorization //token在header中的屬性名
    secret: jwt-token-secret  //生成token的密鑰

創(chuàng)建jwt工具類,方便實(shí)現(xiàn)根據(jù)用戶信息生成token,以及通過token中獲取用戶信息

@Component
@Data
public class JwtTokenUtil implements Serializable {
    private static final long serialVersionUID = -3301605591108950415L;
    @Value("${jwt.secret}")
    private  String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    private Clock clock = DefaultClock.INSTANCE;
    //根據(jù)用戶信息生成token
    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) {
        final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    private Date calculateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + expiration);
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        SecurityUserDetails user = (SecurityUserDetails) userDetails;
        final String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername())
                && !isTokenExpired(token)
        );
    }
    //通過token獲取用戶名username
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    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.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }


    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(clock.now());
    }

    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

}

配置Security

編寫一個(gè)存儲(chǔ)用戶信息的UserDetails的實(shí)現(xiàn)類

@Data
public class SysUser {
    private Integer id;
    private String username;
    private String password;
}
@Data
@EqualsAndHashCode
@Accessors(chain = true) //實(shí)現(xiàn)鏈?zhǔn)絪et方法
public class SecurityUserDetails extends SysUser implements UserDetails {
    //權(quán)限列表
    private Collection<? extends GrantedAuthority> authorities;
    public SecurityUserDetails(String userName,Collection<? extends GrantedAuthority> authorities){
        this.setUsername(userName);
        String encode = new BCryptPasswordEncoder().encode("123456");
        this.setPassword(encode);
        this.setAuthorities(authorities);
    }
    /**
     * 下面這些都返回true
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }
}

提示
因?yàn)橹皇怯涗浺幌氯绾螌?shí)現(xiàn)security+jwt,所以沒有從數(shù)據(jù)庫(kù)中讀取真實(shí)的用戶信息,而是直接將用戶信息和權(quán)限信息寫死測(cè)試。

重寫UserDetailsServiceloadUserByUsername方法實(shí)現(xiàn)具體的認(rèn)證授權(quán)邏輯

@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> authorityList = new ArrayList<>();
        authorityList.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new SecurityUserDetails(username,authorityList);
    }
}

提示
這里直接把用戶的權(quán)限寫死,ROLE_USER表示用戶擁有USER權(quán)限,因?yàn)闄?quán)限都是以ROLE_開頭的。

緊接著創(chuàng)建一個(gè)用戶請(qǐng)求的過濾器,用來攔截用戶請(qǐng)求,分析用戶有沒有該請(qǐng)求的權(quán)限

@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
    private final UserDetailsService userDetailsService;
    private final JwtTokenUtil jwtTokenUtil;
    private final String tokenHeader;

    public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsServiceImpl") UserDetailsService userDetailsService,
                                       JwtTokenUtil jwtTokenUtil,
                                       @Value("${jwt.tokenHeader}") String tokenHeader){
        this.userDetailsService = userDetailsService;
        this.jwtTokenUtil = jwtTokenUtil;
        this.tokenHeader = tokenHeader;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        final String requestHeader = request.getHeader(this.tokenHeader);
        String username = null;
        String authToken = null;
        if(requestHeader != null && requestHeader.startsWith("Bearer ")){
            authToken = requestHeader.substring(7);
            try {
                username = jwtTokenUtil.getUsernameFromToken(authToken);
            }catch (ExpiredJwtException e){
                e.printStackTrace();
            }
        }

        if(username!=null&& SecurityContextHolder.getContext().getAuthentication() == null){
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if(jwtTokenUtil.validateToken(authToken,userDetails)){
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        filterChain.doFilter(request,response);
    }
}

提示
Bearer 必須帶空格,第二個(gè)if判斷就是為了加載到用戶的信息,并且在Security上下文中存儲(chǔ)用戶及用戶的權(quán)限的信息

實(shí)現(xiàn)AuthenticationEntryPoint接口的commence方法,當(dāng)請(qǐng)求沒有攜帶認(rèn)證信息或者說認(rèn)證失敗時(shí),使用我們自己編寫的處理邏輯。

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    }
}

提示
如果請(qǐng)求沒有攜帶認(rèn)證信息或者說認(rèn)證失敗時(shí),會(huì)返回給客戶端401,如果不重寫commence方法,默認(rèn)返回403

接下來編寫Security的核心配置類,重寫WebSecurityConfigurerAdapter中的configure方法

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Autowired
    JwtUserDetailsServiceImpl jwtUserDetailsService;
    @Autowired
    JwtAuthorizationTokenFilter authenticationTokenFilter;
    @Autowired
    @Lazy
    PasswordEncoder passwordEncoder;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .and()
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers(HttpMethod.OPTIONS, "/**").anonymous()
                .anyRequest().authenticated() //除上面以外的都攔截
                .and()
                .csrf().disable() //禁用security自帶的跨域處理
                //讓Security不使用session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

    }

    @Bean
    public PasswordEncoder passwordEncoderBean() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 認(rèn)證邏輯配置
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder);
    }
}

提示
上面的代碼中,.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)表示使用自定義的認(rèn)證失敗處理邏輯。并且配置類中,自定義了用戶密碼的加密方式,configureGlobal方法設(shè)置自定義的loadUserByUsername方法實(shí)現(xiàn)和校驗(yàn)密碼校驗(yàn)的加密方式。

編寫測(cè)試相關(guān)代碼

編寫一個(gè)不需要認(rèn)證授權(quán)就能訪問的登錄接口/login

@RestController
public class LoginController {
    @Autowired
    @Qualifier("jwtUserDetailsServiceImpl")
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @PostMapping("/login")
    public String login(@RequestBody SysUser sysUser, HttpServletRequest request){
        final UserDetails userDetails = userDetailsService.loadUserByUsername(sysUser.getUsername());
        final String token = jwtTokenUtil.generateToken(userDetails);
        return token;
    }
}

編寫一個(gè)需要USER權(quán)限的接口/sys/testUser

@RestController
@RequestMapping("/sys")
public class SysUserController {
    @PreAuthorize("hasAnyRole('USER')")
    @PostMapping(value = "/testUser")
    public String testNeed() {
        return "hello world";
    }
}

測(cè)試

啟動(dòng)SpringBoot項(xiàng)目,對(duì)上面的接口進(jìn)行測(cè)試,首先調(diào)用/login接口登錄并獲取token

請(qǐng)求成功并獲取到jwt生成的token。緊接著調(diào)用需要USER權(quán)限的/testUser,請(qǐng)求時(shí)要在請(qǐng)求頭里面攜帶token

請(qǐng)求成功!

現(xiàn)在來測(cè)試一下失敗的情況,不傳token直接請(qǐng)求

請(qǐng)求失敗,返回401,表示沒有認(rèn)證。再來測(cè)試一下如果將@PreAuthorize("hasAnyRole('USER')")中的權(quán)限改為Admin,然后用剛剛生成的token去請(qǐng)求

@PreAuthorize("hasAnyRole('Admin')")
@PostMapping(value = "/testUser")
public String testNeed() {
    return "hello world";
}

由于token中包含的授權(quán)信息是USER,所以將@PreAuthorize("hasAnyRole('USER')")中的USER改為Admin后,返回了403,表示沒有這個(gè)權(quán)限。

到此這篇關(guān)于SpringBoot Security+JWT簡(jiǎn)單搭建的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)SpringBoot Security JWT搭建內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot實(shí)現(xiàn)給屬性賦值的兩種方式

    SpringBoot實(shí)現(xiàn)給屬性賦值的兩種方式

    在Spring Boot中,配置文件是用來設(shè)置應(yīng)用程序的各種參數(shù)和操作模式的重要部分,Spring Boot支持兩種主要類型的配置文件:properties文件和YAML 文件,這兩種文件都可以用來定義相同的配置,接下來由小編給大家詳細(xì)的介紹一下這兩種方式
    2024-07-07
  • Spring中@EnableScheduling注解的工作原理詳解

    Spring中@EnableScheduling注解的工作原理詳解

    這篇文章主要介紹了Spring中@EnableScheduling注解的工作原理詳解,@EnableScheduling是 Spring Framework 提供的一個(gè)注解,用于啟用Spring的定時(shí)任務(wù)(Scheduling)功能,需要的朋友可以參考下
    2024-01-01
  • JavaWeb實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)(1)

    JavaWeb實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)(1)

    這篇文章主要為大家詳細(xì)介紹了JavaWeb實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)第一篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • 每天練一練Java函數(shù)與算法Math函數(shù)總結(jié)與字符串轉(zhuǎn)換整數(shù)

    每天練一練Java函數(shù)與算法Math函數(shù)總結(jié)與字符串轉(zhuǎn)換整數(shù)

    這篇文章主要介紹了Java函數(shù)與算法Math函數(shù)總結(jié)與字符串轉(zhuǎn)換整數(shù),每天練一練,水平在不知不覺中提高,需要的朋友快過來看看吧
    2021-08-08
  • java中刪除 數(shù)組中的指定元素方法

    java中刪除 數(shù)組中的指定元素方法

    下面小編就為大家?guī)硪黄猨ava中刪除 數(shù)組中的指定元素方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-01-01
  • SpringBoot+log4j2.xml使用application.yml屬性值問題

    SpringBoot+log4j2.xml使用application.yml屬性值問題

    這篇文章主要介紹了SpringBoot+log4j2.xml使用application.yml屬性值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • 深入學(xué)習(xí)java位運(yùn)算的基礎(chǔ)知識(shí)

    深入學(xué)習(xí)java位運(yùn)算的基礎(chǔ)知識(shí)

    位運(yùn)算是直接對(duì)整數(shù)在內(nèi)存中的二進(jìn)制位進(jìn)行操作嗎,位運(yùn)算即可以節(jié)約內(nèi)存,同時(shí)使程序速度更快效率更高。文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,下面我們來一起學(xué)習(xí)下吧
    2019-06-06
  • clickhouse?批量插入數(shù)據(jù)及ClickHouse常用命令詳解

    clickhouse?批量插入數(shù)據(jù)及ClickHouse常用命令詳解

    這篇文章主要介紹了clickhouse?批量插入數(shù)據(jù)及ClickHouse常用命令,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-03-03
  • maven打包本地jar到項(xiàng)目中的方法實(shí)現(xiàn)

    maven打包本地jar到項(xiàng)目中的方法實(shí)現(xiàn)

    本文主要介紹了maven打包本地jar到項(xiàng)目中的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • java數(shù)組輸出的實(shí)例代碼

    java數(shù)組輸出的實(shí)例代碼

    這篇文章主要介紹了java數(shù)組輸出的實(shí)例代碼,有需要的朋友可以參考一下
    2013-12-12

最新評(píng)論

茂名市| 台中县| 墨竹工卡县| 苍梧县| 齐河县| 阿合奇县| 柳州市| 宁明县| 玉树县| 通州区| 永嘉县| 紫金县| 九江县| 平乡县| 水富县| 双流县| 遂川县| 永嘉县| 霍山县| 天长市| 通州区| 兴文县| 涟水县| 措勤县| 济源市| 五台县| 安顺市| 芦溪县| 远安县| 青河县| 巨野县| 凉城县| 江门市| 桐梓县| 沙田区| 辛集市| 福州市| 兰考县| 嵩明县| 泾阳县| 麻江县|