Heim  >  Artikel  >  Java  >  So verwenden Sie SpringBoot+SpringSecurity+JWT, um die Systemauthentifizierung und -autorisierung zu implementieren

So verwenden Sie SpringBoot+SpringSecurity+JWT, um die Systemauthentifizierung und -autorisierung zu implementieren

WBOY
WBOYnach vorne
2023-05-14 20:16:041751Durchsuche

1. Einführung in Spring Security

Spring Security ist ein Kernprojekt von Spring. Es ist ein leistungsstarkes und hochgradig anpassbares Authentifizierungs- und Zugriffskontroll-Framework. Es bietet Authentifizierungs- und Autorisierungsfunktionen sowie Schutz vor häufigen Angriffen und ist zum De-facto-Standard für den Schutz von Spring-basierten Anwendungen geworden.

Spring Boot bietet eine automatische Konfiguration, die durch Einführung der Starter-Abhängigkeit genutzt werden kann.
Zusammenfassung der Spring Security-Funktionen:

  • Es ist einfach zu verwenden, bietet Spring Boot-Starterabhängigkeiten und lässt sich problemlos in Spring Boot-Projekte integrieren.

  • Professionell, bietet CSRF-Schutz, Clickjacking-Schutz, XSS-Schutz usw. und bietet verschiedene Sicherheits-Header-Integrationen (X-XSS-Schutz, X-Frame-Optionen usw.).

  • Passwortverschlüsselter Speicher, unterstützt mehrere Verschlüsselungsalgorithmen

  • Extrem skalierbar und anpassbar

  • OAuth3 JWT-Authentifizierungsunterstützung

  • … Einführung in JWT

  • JWT (Json-Web-Token ) ist ein JSON-basierter offener Standard (RFC 7519), der für die Übertragung von Ansprüchen zwischen Netzwerkanwendungsumgebungen implementiert ist. Der Token ist kompakt und sicher konzipiert und eignet sich besonders für verteilte Sites (SSO). JWT-Ansprüche werden im Allgemeinen verwendet, um authentifizierte Benutzeridentitätsinformationen zwischen Identitätsanbietern und Dienstanbietern weiterzugeben, um den Erhalt von Ressourcen von Ressourcenservern zu erleichtern. Sie können auch einige zusätzliche Anspruchsinformationen hinzufügen, die für andere Geschäftslogiken erforderlich sind (z. B. Berechtigungsinformationen). Sobald einem Benutzer ein Token gewährt wird, kann der Benutzer über das Token auf Ressourcen auf dem Server zugreifen.

3. Spring Boot integriert Spring Security

Beachten Sie, dass dieser Artikel die Verwendung von JDK- und Spring Boot-Versionen wie folgt demonstriert:

Spring Boot: 2.7.2

JDK: 11
Verschiedene Spring Boot-Versionen haben unterschiedliche Konfigurationen, aber die Die Prinzipien sind die gleichen.



Fügen Sie der pom.xml-Datei des Spring Boot-Projekts die folgenden Abhängigkeiten hinzu:

<!-- 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>

Die beiden oben genannten Abhängigkeiten reichen aus.

4. Konfigurieren Sie Spring Security für die Verwendung der JWT-Authentifizierung.

Hinweis:

Verschiedene Spring Boot-Versionen haben unterschiedliche Konfigurationen, aber das Prinzip ist das gleiche.

Konfiguriert hauptsächlich HttpSecurity Bean, um SecurityFilterBean zu generieren. Die Konfiguration ist wie folgt:

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

Die obige Konfiguration erfordert die Generierung eines RSA-Schlüsselpaars im Ressourcenverzeichnis des Spring Boot-Projekts. Sie können die folgende Website zum Generieren verwenden: http://tools.jb51.net/password/rsa_encode/, Hinweis: Das Schlüsselformat verwendet PKCS#8 und das Passwort für den privaten Schlüssel ist leer.


Noch etwas zu beachten: Ich habe die Wertinjektion von Spring Boot im Code verwendet:

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

Sind Sie neugierig, wie Spring Boot die Datei, die der Zeichenfolge in der Yaml-Datei entspricht, in RSAPublicKey und RSAPrivateKey konvertiert?

Tatsächlich hat Spring Security die Verarbeitung für uns durchgeführt. Es hat uns geholfen, einen ResourceKeyConverterAdapter in Spring Security zu implementieren. Für ein tieferes Verständnis können Sie den entsprechenden Quellcode lesen.

Unser Projekt unterstützt jetzt die JWT-Authentifizierung.
Aber der Benutzer muss im Anforderungsheader eine legale JWT-Autorisierung mitführen, um die Authentifizierung zu bestehen und dann auf die Serverressourcen zuzugreifen. Wie kann man dem Benutzer also ein legales JWT ausstellen?

Es ist sehr einfach. Es kann eine Anmeldeschnittstelle bereitstellen, dem Benutzer die Eingabe des Benutzernamens und des Kennworts ermöglichen und nach erfolgreichem Abgleich das Token ausstellen.


Tatsächlich ist dies nicht erforderlich. Es gibt andere Möglichkeiten, z. B. den Aufruf einer Drittanbieterschnittstelle. Nachdem der Antrag genehmigt wurde, können wir einen Antrag stellen Token. Dieser Vorgang ist derselbe wie die Ausgabe eines Tokens nach der oben genannten Anmeldung. Beide erhalten einen Token auf legalem Weg!

5. Implementieren Sie die Anmeldeschnittstelle

Die Anmeldeschnittstelle hat nur einen Zweck, nämlich die Ausgabe von Token an legitime Benutzer!
Login-API-Schnittstelle:

@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);
    }
}

Login-Logik-Implementierung:

@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();
    }
}

Anderer Code, der nicht zum Kern gehört, wird hier nicht veröffentlicht. Weitere Informationen finden Sie unter https://github.com/cloudgyb/. Frühling -Sicherheitsstudie-jwt.

6. Testen Sie mit Postman:

Wenn Sie das falsche Passwort verwenden, wird der Statuscode 401 Unauthorized zurückgegeben, der darauf hinweist, dass unsere Authentifizierung fehlgeschlagen ist.

Verwenden Sie den richtigen Benutzernamen und das richtige Passwort:

So verwenden Sie SpringBoot+SpringSecurity+JWT, um die Systemauthentifizierung und -autorisierung zu implementieren

JWT-Token zurückgegeben.

Zu diesem Zeitpunkt hat der Client das legale Token erhalten und kann dann auf die Ressourcen zugreifen, auf die er Zugriff auf dem Server hat.

Ich habe eine Testschnittstelle geschrieben: So verwenden Sie SpringBoot+SpringSecurity+JWT, um die Systemauthentifizierung und -autorisierung zu implementieren

@RestController
public class HelloController {

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

Für diese Schnittstelle muss der Benutzer über die Berechtigung „Test“ verfügen, der angemeldete Benutzer verfügt jedoch nicht über diese Berechtigung (nur eine App-Berechtigung). Rufen Sie zu diesem Zeitpunkt die Schnittstelle auf:

Zuerst , melden Sie sich im vorherigen Schritt an. Fügen Sie den Token in Token ein:


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

@RestController
public class HelloController {

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

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

So verwenden Sie SpringBoot+SpringSecurity+JWT, um die Systemauthentifizierung und -autorisierung zu implementieren

Das obige ist der detaillierte Inhalt vonSo verwenden Sie SpringBoot+SpringSecurity+JWT, um die Systemauthentifizierung und -autorisierung zu implementieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen