Rumah  >  Artikel  >  Java  >  Cara menggunakan SpringBoot+SpringSecurity+JWT untuk melaksanakan pengesahan dan kebenaran sistem

Cara menggunakan SpringBoot+SpringSecurity+JWT untuk melaksanakan pengesahan dan kebenaran sistem

WBOY
WBOYke hadapan
2023-05-14 20:16:041754semak imbas

1. Pengenalan kepada Spring Security

Spring Security ialah projek teras Spring Ia adalah rangka kerja pengesahan dan kawalan akses yang berkuasa dan boleh disesuaikan. Ia menyediakan keupayaan pengesahan dan kebenaran serta perlindungan terhadap serangan biasa, dan ia telah menjadi standard de facto untuk melindungi aplikasi berasaskan spring.

Spring Boot menyediakan konfigurasi automatik, yang boleh digunakan dengan memperkenalkan kebergantungan pemula.
Ringkasan ciri Spring Security:

  • Mudah digunakan, menyediakan kebergantungan pemula Spring Boot dan mudah untuk disepadukan dengan projek Spring Boot.

  • Profesional, menyediakan perlindungan CSRF, perlindungan clickjacking, perlindungan XSS, dll., dan menyediakan pelbagai penyepaduan pengepala keselamatan (X-XSS-Protection, X-Frame-Options, dll.).

  • Storan disulitkan kata laluan, menyokong berbilang algoritma penyulitan

  • Sangat boleh skala dan boleh disesuaikan

  • OAuth3 JWTO sokongan pengesahan

2 pengenalan JWT

JWT (token web Json ), ialah JSON. -standard terbuka berasaskan (RFC 7519) dilaksanakan untuk memindahkan tuntutan antara persekitaran aplikasi rangkaian Token ini direka bentuk untuk padat dan selamat, terutamanya sesuai untuk senario log masuk tunggal (SSO) tapak yang diedarkan. Tuntutan JWT biasanya digunakan untuk menyampaikan maklumat identiti pengguna yang disahkan antara pembekal identiti dan penyedia perkhidmatan untuk memudahkan mendapatkan sumber daripada pelayan sumber Mereka juga boleh menambah beberapa maklumat tuntutan tambahan yang diperlukan untuk logik perniagaan lain (contohnya, maklumat kebenaran). Sebaik sahaja pengguna diberikan token, pengguna boleh mengakses sumber pada pelayan melalui token.

3. Spring Boot mengintegrasikan Spring Security

Perhatikan bahawa artikel ini menunjukkan penggunaan versi JDK dan Spring Boot seperti berikut:
Spring Boot: 2.7.2
JDK: 11
Versi Spring Boot yang berbeza mempunyai konfigurasi yang berbeza, tetapi prinsipnya adalah sama.

Tambah kebergantungan berikut pada fail pom.xml projek Spring Boot:

<!-- Spring Security的Spring boot starter,引入后将自动启动Spring Security的自动配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 下面的依赖包含了OAuth3 JWT认证实现 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth3-resource-server</artifactId>
</dependency>

Dua kebergantungan di atas sudah memadai.

4. Konfigurasikan Spring Security untuk menggunakan pengesahan JWT

Nota: Versi Spring Boot yang berbeza mempunyai konfigurasi yang berbeza, tetapi prinsipnya adalah sama But Spring: 2.7.2.

Terutamanya mengkonfigurasi HttpSecurity Bean untuk menjana SecurityFilterBean Konfigurasi adalah seperti berikut:

import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth3.jwt.JwtDecoder;
import org.springframework.security.oauth3.jwt.JwtEncoder;
import org.springframework.security.oauth3.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth3.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth3.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth3.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth3.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth3.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.SecurityFilterChain;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

/**
 * Spring Security 配置
 *
 * @author cloudgyb
 * @since 2022/7/30 18:31
 */
@Configuration(proxyBeanMethods = false)
@EnableMethodSecurity
public class WebSecurityConfigurer {
    //使用RSA对JWT做签名,所以这里需要一对秘钥。
    //秘钥文件的路径在application.yml文件中做了配置(具体配置在下面)。
    @Value("${jwt.public.key}")
    private RSAPublicKey key; 
    @Value("${jwt.private.key}")
    private RSAPrivateKey priv;

     /**
      * 构建SecurityFilterChain bean
      */
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        //"/login"是系统的登录接口,所以需要匿名可访问
        http.authorizeRequests().antMatchers("/login").anonymous();
        //其他请求都需认证后才能访问
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                
                //采用JWT认证无需session保持,所以禁用掉session管理器
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                //login接口可能来自其他站点,所以对login不做csrf防护
                .csrf((csrf) -> csrf.ignoringAntMatchers("/login"))
                //配置认证方式为JWT,并且配置了一个JWT认证装换器,用于去掉解析权限时的SCOOP_前缀
                .oauth3ResourceServer().jwt().jwtAuthenticationConverter(
                        JwtAuthenticationConverter()
                );
        //配置认证失败或者无权限时的处理器
        http.exceptionHandling((exceptions) -> exceptions
                .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
        );
         //根据配置生成SecurityFilterChain对象
        return http.build();
    }


    /**
     * JWT解码器,用于认证时的JWT解码 
     */
    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(this.key).build();
    }
    /**
     * JWT编码器,生成JWT
     */
    @Bean
    JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
        JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwks);
    }
    
    /**
     * JWT认证解码时,去掉Spring Security对权限附带的默认前缀SCOOP_
     */
    @Bean
    JwtAuthenticationConverter JwtAuthenticationConverter() {
        final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
        final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}

application.yml

jwt:
  private.key: classpath:app.key
  public.key: classpath:app.pub

Keperluan konfigurasi di atas. yang perlu dilakukan dalam Spring Boot Jana sepasang kunci RSA dalam direktori Sumber projek.
Anda boleh menggunakan tapak web berikut untuk menjana: http://tools.jb51.net/password/rsa_encode/, Nota: Format kunci menggunakan PKCS#8 dan kata laluan kunci persendirian kosong.

Satu perkara lagi perlu dijelaskan saya menggunakan suntikan nilai Spring Boot dalam kod:

@Value("${jwt.public.key}")
 private RSAPublicKey key; 
@Value("${jwt.private.key}")
private RSAPrivateKey priv;

Adakah anda ingin tahu tentang apa Spring Boot. adalah? Bagaimana untuk menukar fail yang sepadan dengan rentetan dalam fail yaml kepada RSAPublicKey dan RSAPrivateKey?
Malah, Spring Security melakukan pemprosesan untuk kami. Ia membantu kami melaksanakan penukar ResourceKeyConverterAdapter dalam Spring Security Anda boleh membaca kod sumber yang berkaitan untuk pemahaman yang lebih mendalam.

Setakat ini projek kami telah menyokong pengesahan JWT.
Tetapi pengguna perlu membawa JWT yang sah dalam pengepala permintaan Keizinan untuk lulus pengesahan dan kemudian mengakses sumber pelayan Jadi bagaimana untuk mengeluarkan JWT yang sah kepada pengguna?
Ia sangat mudah Anda boleh menyediakan antara muka log masuk, membenarkan pengguna memasukkan nama pengguna dan kata laluan, dan mengeluarkan token selepas padanan yang berjaya.

Malah, tidak perlu melakukan ini, seperti memanggil antara muka pihak ketiga adalah dengan memohon kepada pihak ketiga terlebih dahulu diluluskan, kita boleh mendapatkan token. Proses ini adalah sama seperti pengeluaran token selepas log masuk diluluskan di atas. Kedua-duanya memperoleh token melalui cara yang sah!

5. Laksanakan antara muka log masuk

Antara muka log masuk hanya mempunyai satu tujuan, iaitu untuk mengeluarkan token kepada pengguna yang sah!
Antara muka API log masuk:

@RestController
public class SysLoginController {
    private final SysLoginService sysLoginService;

    public SysLoginController(SysLoginService sysLoginService) {
        this.sysLoginService = sysLoginService;
    }

    @PostMapping("/login")
    public String login(@RequestBody LoginInfo loginInfo) {
        return sysLoginService.login(loginInfo);
    }
}

Pelaksanaan logik log masuk:

@Service
public class SysLoginService {
    private final JwtEncoder jwtEncoder;
    private final SpringSecurityUserDetailsService springSecurityUserDetailsService;

    public SysLoginService(JwtEncoder jwtEncoder, SpringSecurityUserDetailsService springSecurityUserDetailsService) {
        this.jwtEncoder = jwtEncoder;
        this.springSecurityUserDetailsService = springSecurityUserDetailsService;
    }

    public String login(LoginInfo loginInfo) {
        //从用户信息存储库中获取用户信息
        final UserDetails userDetails = springSecurityUserDetailsService.loadUserByUsername(loginInfo.getUsername());
        final String password = userDetails.getPassword();
        //匹配密码,匹配成功生成JWT令牌
        if (password.equals(loginInfo.getPassword())) {
            return generateToken(userDetails);
        }
        //密码不匹配,抛出异常,Spring Security发现抛出该异常后会将http响应状态码设置为401 unauthorized
        throw new BadCredentialsException("密码错误!");
    }

    private String generateToken(UserDetails userDetails) {
        Instant now = Instant.now();
        //JWT过期时间为36000秒,也就是600分钟,10小时
        long expiry = 36000L;
        String scope = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(" "));
         //将用户权限信息使用空格分割拼为字符串,放到JWT的payload的scope字段中,注意不要改变scope这个属性,这是Spring Security OAuth3 JWT默认处理方式,在JWT解码时需要读取该字段,转为用户的权限信息!
        JwtClaimsSet claims = JwtClaimsSet.builder()
                .issuer("self")
                .issuedAt(now)
                .expiresAt(now.plusSeconds(expiry))
                .subject(userDetails.getUsername())
                .claim("scope", scope)
                .build();
        return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
    }
}

Kod bukan teras lain tidak akan disiarkan di sini Saya meletakkan kod pada github https://github.com/cloudgyb/spring-security-study-jwt.

6. Ujian

Gunakan posmen untuk menguji:
Menggunakan kata laluan yang salah akan mengembalikan kod status 401 Tanpa kebenaran, menunjukkan bahawa pengesahan kami gagal!

Cara menggunakan SpringBoot+SpringSecurity+JWT untuk melaksanakan pengesahan dan kebenaran sistem

Menggunakan nama pengguna dan kata laluan yang betul:

Cara menggunakan SpringBoot+SpringSecurity+JWT untuk melaksanakan pengesahan dan kebenaran sistem

Token JWT telah dikembalikan.

Pada ketika ini, pelanggan telah memperoleh token undang-undang, dan kemudian boleh mengakses sumber pada pelayan yang boleh diakses olehnya.
Saya menulis antara muka ujian:

@RestController
public class HelloController {

    @GetMapping("/")
    @PreAuthorize("hasAuthority(&#39;test&#39;)")
    public String hello(Authentication authentication) {
        return "Hello, " + authentication.getName() + "!";
    }
}

Antara muka ini memerlukan pengguna mempunyai kebenaran "ujian", tetapi pengguna yang log masuk tidak mempunyai kebenaran ini (hanya satu kebenaran apl pada masa ini,). antara muka dipanggil:
Mula-mula tampal token yang diperolehi daripada langkah log masuk sebelumnya ke dalam token:

Cara menggunakan SpringBoot+SpringSecurity+JWT untuk melaksanakan pengesahan dan kebenaran sistem

我们发送请求得到了403 Forbidden的响应,意思就是我们没有访问权限,此时我们将接口权限改为“app”:

@RestController
public class HelloController {

    @GetMapping("/")
    @PreAuthorize("hasAuthority(&#39;app&#39;)")
    public String hello(Authentication authentication) {
        return "Hello, " + authentication.getName() + "!";
    }
}

重启项目。再次发起请求:

Cara menggunakan SpringBoot+SpringSecurity+JWT untuk melaksanakan pengesahan dan kebenaran sistem

Atas ialah kandungan terperinci Cara menggunakan SpringBoot+SpringSecurity+JWT untuk melaksanakan pengesahan dan kebenaran sistem. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam