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

使用SpringBoot進(jìn)行身份驗(yàn)證和授權(quán)的示例詳解

 更新時(shí)間:2023年11月30日 16:56:42   作者:happyEnding  
在廣闊的 Web 開(kāi)發(fā)世界中,身份驗(yàn)證是每個(gè)數(shù)字領(lǐng)域的守護(hù)者,在本教程中,我們將了解如何以本機(jī)方式保護(hù)、驗(yàn)證和授權(quán) Spring-Boot 應(yīng)用程序的用戶,并遵循框架的良好實(shí)踐,希望對(duì)大家有所幫助

第一步

為了增強(qiáng)我們的應(yīng)用程序的安全性,我們需要兩個(gè)依賴項(xiàng)pom.xml,第一個(gè)是spring-security,另一個(gè)將幫助我們創(chuàng)建和驗(yàn)證 jwt 令牌。

//pom.xml
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
   <groupId>com.auth0</groupId>
   <artifactId>java-jwt</artifactId>
   <version>4.4.0</version>
</dependency>

用戶實(shí)體和存儲(chǔ)庫(kù)

首先,我們需要一個(gè)枚舉來(lái)表示用戶角色,這將幫助我們定義應(yīng)用程序中每個(gè)用戶的權(quán)限。

// enums/UserRole.java
public enum UserRole {
  ADMIN("admin"),
  USER("user");

  private String role;

  UserRole(String role) {
    this.role = role;
  }

  public String getValue() {
    return role;
  }
}

在枚舉中,我們有兩個(gè)代表性角色:ADMINUSER,該ADMIN角色將有權(quán)訪問(wèn)我們應(yīng)用程序的所有接口,而該USER角色只能訪問(wèn)特定接口。

用戶實(shí)體將是我們身份驗(yàn)證系統(tǒng)的核心,它將保存用戶的憑據(jù)和用戶擁有的角色。我們將實(shí)現(xiàn)UserDetails接口來(lái)表示我們的用戶實(shí)體,該接口由 spring security 包提供,并且是在 spring-boot 應(yīng)用程序中表示用戶實(shí)體的推薦方法。

// entities/UserEntity.java
@Table()
@Entity(name = "users")
@Getter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class User implements UserDetails {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String login;

  private String password;

  @Enumerated(EnumType.STRING)
  private UserRole role;

  public User(String login, String password, UserRole role) {
    this.login = login;
    this.password = password;
    this.role = role;
  }

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    if (this.role == UserRole.ADMIN) {
      return List.of(new SimpleGrantedAuthority("ROLE_ADMIN"), new SimpleGrantedAuthority("ROLE_USER"));
    }
    return List.of(new SimpleGrantedAuthority("ROLE_USER"));
  }

  @Override
  public String getUsername() {
    return login;
  }

  @Override
  public boolean isAccountNonExpired() {
    return true;
  }

  @Override
  public boolean isAccountNonLocked() {
    return true;
  }

  @Override
  public boolean isCredentialsNonExpired() {
    return true;
  }

  @Override
  public boolean isEnabled() {
    return true;
  }
}

它有很多我們可以覆蓋的方法來(lái)自定義身份驗(yàn)證過(guò)程,您也可以在數(shù)據(jù)庫(kù)中實(shí)現(xiàn)這些屬性,但現(xiàn)在我們只使用使我們的身份驗(yàn)證系統(tǒng)工作所需的方法:id、username和。password``role

對(duì)于用戶存儲(chǔ)庫(kù),我們有以下代碼:

// repositories/UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
  UserDetails findByLogin(String login);
}

擴(kuò)展后,JpaRepository我們將可以訪問(wèn)許多方法來(lái)操作數(shù)據(jù)庫(kù)中的用戶。此外,findByLoginSpring Security 將使用該方法在數(shù)據(jù)庫(kù)中查找用戶并驗(yàn)證憑據(jù)。

密鑰

我們需要定義一個(gè)密鑰來(lái)簽署我們的令牌,該密鑰將用于驗(yàn)證和生成令牌簽名。我們將使用@Value注釋從文件中獲取密鑰application.yml。在文件中,application.yml我們將密鑰定義為環(huán)境變量,這將幫助我們保證密鑰的安全并遠(yuǎn)離源代碼。

//.env
JWT_SECRET="yoursecret"

在我們的application.yml

// resources/application.yml
security:
  jwt:
    token:
      secret-key: ${JWT_SECRET}

為了讓 spring-boot 應(yīng)用程序讀取環(huán)境變量,我們需要PropertySource在主類中聲明注釋來(lái)指示.env文件所在的位置。在我們的例子中,它位于項(xiàng)目的根目錄中,因此我們將使用該user.dir變量來(lái)獲取項(xiàng)目根路徑。主類將如下所示:

@SpringBootApplication
@PropertySource("file:${user.dir}/.env")
public class SpringAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAuthApplication.class, args);
    }
}

最后我們可以定義我們的令牌提供者類,該類將負(fù)責(zé)生成和驗(yàn)證我們的令牌。

// config/auth/TokenProvider.java
@Service
public class TokenProvider {
  @Value("${security.jwt.token.secret-key}")
  private String JWT_SECRET;

  public String generateAccessToken(User user) {
    try {
      Algorithm algorithm = Algorithm.HMAC256(JWT_SECRET);
      return JWT.create()
          .withSubject(user.getUsername())
          .withClaim("username", user.getUsername())
          .withExpiresAt(genAccessExpirationDate())
          .sign(algorithm);
    } catch (JWTCreationException exception) {
      throw new JWTCreationException("Error while generating token", exception);
    }
  }

  public String validateToken(String token) {
    try {
      Algorithm algorithm = Algorithm.HMAC256(JWT_SECRET);
      return JWT.require(algorithm)
          .build()
          .verify(token)
          .getSubject();
    } catch (JWTVerificationException exception) {
      throw new JWTVerificationException("Error while validating token", exception);
    }
  }

  private Instant genAccessExpirationDate() {
    return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00"));
  }
}

在本文中,generateAccessToken我們定義了一個(gè)算法來(lái)簽署我們的令牌、令牌的主題和到期日期,并返回一個(gè)新的令牌。在該validateToken方法中,我們驗(yàn)證令牌簽名并返回令牌的主題。

安全過(guò)濾器

然后我們需要定義一個(gè)過(guò)濾器來(lái)攔截請(qǐng)求并驗(yàn)證令牌。我們將擴(kuò)展OncePerRequestFilterspring 安全類來(lái)攔截請(qǐng)求并驗(yàn)證令牌。

// config/auth/SecurityFilter.java
@Component
public class SecurityFilter extends OncePerRequestFilter {
  @Autowired
  TokenProvider tokenService;
  @Autowired
  UserRepository userRepository;

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
    var token = this.recoverToken(request);
    if (token != null) {
      var login = tokenService.validateToken(token);
      var user = userRepository.findByLogin(login);
      var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
      SecurityContextHolder.getContext().setAuthentication(authentication);
    }
    filterChain.doFilter(request, response);
  }

  private String recoverToken(HttpServletRequest request) {
    var authHeader = request.getHeader("Authorization");
    if (authHeader == null)
      return null;
    return authHeader.replace("Bearer ", "");
  }
}

在該doFilterInternal方法中,我們從請(qǐng)求中恢復(fù)令牌,使用輔助方法從字符串中刪除“Bearer” recoverToken,驗(yàn)證令牌并在SecurityContextHolder. 這SecurityContextHolder是一個(gè) Spring Security 類,它保存當(dāng)前請(qǐng)求的身份驗(yàn)證,因此我們可以訪問(wèn)控制器中的用戶信息。

認(rèn)證配置

這里我們需要定義一些更必要的方法來(lái)使我們的身份驗(yàn)證系統(tǒng)正常工作。在頂部,我們有Configuration@EnableWebSecurity注釋,用于在我們的應(yīng)用程序中啟用網(wǎng)絡(luò)安全。然后我們定義SecurityFilterChainbean 來(lái)定義將受我們的身份驗(yàn)證系統(tǒng)保護(hù)的接口。

// config/AuthConfig.java
@Configuration
@EnableWebSecurity
public class AuthConfig {
  @Autowired
  SecurityFilter securityFilter;

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    return httpSecurity
        .csrf(csrf -> csrf.disable())
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(authorize -> authorize
            .requestMatchers(HttpMethod.POST, "/api/v1/auth/*").permitAll()
            .requestMatchers(HttpMethod.POST, "/api/v1/books").hasRole("ADMIN")
            .anyRequest().authenticated())
        .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
        .build();
  }

  @Bean
  AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
      throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
  }

  @Bean
  PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
}

在該authorizeHttpRequests方法中,我們定義將受到保護(hù)的接口以及有權(quán)訪問(wèn)每個(gè)接口的角色。在我們的例子中/api/v1/auth/*,接口將是公共的,/api/v1/books接口將受到保護(hù),并且只有具有該角色的用戶ADMIN才能訪問(wèn)它。其他接口將受到保護(hù),只有經(jīng)過(guò)身份驗(yàn)證的用戶才能訪問(wèn)它。

在該addFilterBefore方法中,我們定義之前創(chuàng)建的過(guò)濾器。最后,我們定義了使身份驗(yàn)證系統(tǒng)正常工作所需的AuthenticationManager和beans。PasswordEncoder

授權(quán) DTO

我們需要兩個(gè) DTO 來(lái)接收用戶憑據(jù),并需要另一個(gè) DTO 在用戶登錄時(shí)返回令牌。

// dtos/SignUpDto.java
public record SignUpDto(
    String login,
    String password,
    UserRole role) {
}
// dtos/SignInDto.java
public record SignInDto(
    String login,
    String password) {
}
// dtos/JwtDto.java
public record JwtDto(
    String accessToken) {
}

認(rèn)證服務(wù)

這里我們定義服務(wù)實(shí)現(xiàn)UserDetailsService,它將負(fù)責(zé)創(chuàng)建用戶并將其保存在數(shù)據(jù)庫(kù)中或通過(guò)用戶名加載用戶信息。

// services/AuthService.java
@Service
public class AuthService implements UserDetailsService {

  @Autowired
  UserRepository repository;

  @Override
  public UserDetails loadUserByUsername(String username) {
    var user = repository.findByLogin(username);
    return user;
  }

  public UserDetails signUp(SignUpDto data) throws InvalidJwtException {
    if (repository.findByLogin(data.login()) != null) {
      throw new InvalidJwtException("Username already exists");
    }
    String encryptedPassword = new BCryptPasswordEncoder().encode(data.password());
    User newUser = new User(data.login(), encryptedPassword, data.role());
    return repository.save(newUser);
  }
}

在該signUp方法中,我們檢查用戶名是否已注冊(cè),然后使用 加密密碼BCryptPasswordEncoder并保存用戶信息。

認(rèn)證Controller

最后我們定義身份驗(yàn)證控制器。它將負(fù)責(zé)接收請(qǐng)求、驗(yàn)證用戶身份并生成令牌。

// controllers/AuthController.java
@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {
  @Autowired
  private AuthenticationManager authenticationManager;
  @Autowired
  private AuthService service;
  @Autowired
  private TokenProvider tokenService;

  @PostMapping("/signup")
  public ResponseEntity<?> signUp(@RequestBody @Valid SignUpDto data) {
    service.signUp(data);
    return ResponseEntity.status(HttpStatus.CREATED).build();
  }

  @PostMapping("/signin")
  public ResponseEntity<JwtDto> signIn(@RequestBody @Valid SignInDto data) {
    var usernamePassword = new UsernamePasswordAuthenticationToken(data.login(), data.password());
    var authUser = authenticationManager.authenticate(usernamePassword);
    var accessToken = tokenService.generateAccessToken((User) authUser.getPrincipal());
    return ResponseEntity.ok(new JwtDto(accessToken));
  }
}

在該signUp方法中我們接收用戶數(shù)據(jù),創(chuàng)建一個(gè)新用戶并將其保存在數(shù)據(jù)庫(kù)中。在該signIn方法中,我們接收用戶憑據(jù),使用 驗(yàn)證用戶身份AuthenticationManager,并生成令牌。

測(cè)試身份驗(yàn)證

要?jiǎng)?chuàng)建新用戶,我們POST/api/v1/auth/signup接口發(fā)送請(qǐng)求,請(qǐng)求正文包含登錄名、密碼和可用角色之一(USER 或 ADMIN):

{
  "login": "myusername",
  "password": "123456",
  "role": "USER"
}

為了檢索身份驗(yàn)證令牌,我們POST使用此用戶登錄名和密碼向/api/v1/auth/signin接口發(fā)送請(qǐng)求。

為了測(cè)試我們的身份驗(yàn)證系統(tǒng),我們將創(chuàng)建一個(gè)帶有兩個(gè)接口的簡(jiǎn)單書(shū)籍控制器,一個(gè)用于創(chuàng)建一本Book,另一個(gè)用于列出所有Book。

@RestController
@RequestMapping("/api/v1/books")
public class BookController {

  @GetMapping
  public ResponseEntity<List<String>> findAll() {
    return ResponseEntity.ok(List.of("Book1", "Book2", "Book3"));
  }

  @PostMapping
  public ResponseEntity<String> create(@RequestBody String data) {
    return ResponseEntity.ok(data);
  }
} 

/api/v1/books接口中,該GET方法將對(duì)具有角色的用戶可用USER,并且該POST方法將受到保護(hù),并且只有具有該角色的用戶ADMIN才能創(chuàng)建書(shū)籍。

以上就是使用SpringBoot進(jìn)行身份驗(yàn)證和授權(quán)的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot身份驗(yàn)證授權(quán)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論