使用SpringBoot進行身份驗證和授權的示例詳解
第一步
為了增強我們的應用程序的安全性,我們需要兩個依賴項pom.xml,第一個是spring-security,另一個將幫助我們創(chuàng)建和驗證 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>
用戶實體和存儲庫
首先,我們需要一個枚舉來表示用戶角色,這將幫助我們定義應用程序中每個用戶的權限。
// enums/UserRole.java
public enum UserRole {
ADMIN("admin"),
USER("user");
private String role;
UserRole(String role) {
this.role = role;
}
public String getValue() {
return role;
}
}
在枚舉中,我們有兩個代表性角色:ADMIN和USER,該ADMIN角色將有權訪問我們應用程序的所有接口,而該USER角色只能訪問特定接口。
用戶實體將是我們身份驗證系統(tǒng)的核心,它將保存用戶的憑據(jù)和用戶擁有的角色。我們將實現(xiàn)UserDetails接口來表示我們的用戶實體,該接口由 spring security 包提供,并且是在 spring-boot 應用程序中表示用戶實體的推薦方法。
// 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;
}
}
它有很多我們可以覆蓋的方法來自定義身份驗證過程,您也可以在數(shù)據(jù)庫中實現(xiàn)這些屬性,但現(xiàn)在我們只使用使我們的身份驗證系統(tǒng)工作所需的方法:id、username和。password``role
對于用戶存儲庫,我們有以下代碼:
// repositories/UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
UserDetails findByLogin(String login);
}
擴展后,JpaRepository我們將可以訪問許多方法來操作數(shù)據(jù)庫中的用戶。此外,findByLoginSpring Security 將使用該方法在數(shù)據(jù)庫中查找用戶并驗證憑據(jù)。
密鑰
我們需要定義一個密鑰來簽署我們的令牌,該密鑰將用于驗證和生成令牌簽名。我們將使用@Value注釋從文件中獲取密鑰application.yml。在文件中,application.yml我們將密鑰定義為環(huán)境變量,這將幫助我們保證密鑰的安全并遠離源代碼。
//.env JWT_SECRET="yoursecret"
在我們的application.yml:
// resources/application.yml
security:
jwt:
token:
secret-key: ${JWT_SECRET}
為了讓 spring-boot 應用程序讀取環(huán)境變量,我們需要PropertySource在主類中聲明注釋來指示.env文件所在的位置。在我們的例子中,它位于項目的根目錄中,因此我們將使用該user.dir變量來獲取項目根路徑。主類將如下所示:
@SpringBootApplication
@PropertySource("file:${user.dir}/.env")
public class SpringAuthApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAuthApplication.class, args);
}
}
最后我們可以定義我們的令牌提供者類,該類將負責生成和驗證我們的令牌。
// 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我們定義了一個算法來簽署我們的令牌、令牌的主題和到期日期,并返回一個新的令牌。在該validateToken方法中,我們驗證令牌簽名并返回令牌的主題。
安全過濾器
然后我們需要定義一個過濾器來攔截請求并驗證令牌。我們將擴展OncePerRequestFilterspring 安全類來攔截請求并驗證令牌。
// 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方法中,我們從請求中恢復令牌,使用輔助方法從字符串中刪除“Bearer” recoverToken,驗證令牌并在SecurityContextHolder. 這SecurityContextHolder是一個 Spring Security 類,它保存當前請求的身份驗證,因此我們可以訪問控制器中的用戶信息。
認證配置
這里我們需要定義一些更必要的方法來使我們的身份驗證系統(tǒng)正常工作。在頂部,我們有Configuration和@EnableWebSecurity注釋,用于在我們的應用程序中啟用網(wǎng)絡安全。然后我們定義SecurityFilterChainbean 來定義將受我們的身份驗證系統(tǒng)保護的接口。
// 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方法中,我們定義將受到保護的接口以及有權訪問每個接口的角色。在我們的例子中/api/v1/auth/*,接口將是公共的,/api/v1/books接口將受到保護,并且只有具有該角色的用戶ADMIN才能訪問它。其他接口將受到保護,只有經(jīng)過身份驗證的用戶才能訪問它。
在該addFilterBefore方法中,我們定義之前創(chuàng)建的過濾器。最后,我們定義了使身份驗證系統(tǒng)正常工作所需的AuthenticationManager和beans。PasswordEncoder
授權 DTO
我們需要兩個 DTO 來接收用戶憑據(jù),并需要另一個 DTO 在用戶登錄時返回令牌。
// 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) {
}
認證服務
這里我們定義服務實現(xiàn)UserDetailsService,它將負責創(chuàng)建用戶并將其保存在數(shù)據(jù)庫中或通過用戶名加載用戶信息。
// 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方法中,我們檢查用戶名是否已注冊,然后使用 加密密碼BCryptPasswordEncoder并保存用戶信息。
認證Controller
最后我們定義身份驗證控制器。它將負責接收請求、驗證用戶身份并生成令牌。
// 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)建一個新用戶并將其保存在數(shù)據(jù)庫中。在該signIn方法中,我們接收用戶憑據(jù),使用 驗證用戶身份AuthenticationManager,并生成令牌。
測試身份驗證
要創(chuàng)建新用戶,我們POST向/api/v1/auth/signup接口發(fā)送請求,請求正文包含登錄名、密碼和可用角色之一(USER 或 ADMIN):
{
"login": "myusername",
"password": "123456",
"role": "USER"
}
為了檢索身份驗證令牌,我們POST使用此用戶登錄名和密碼向/api/v1/auth/signin接口發(fā)送請求。
為了測試我們的身份驗證系統(tǒng),我們將創(chuàng)建一個帶有兩個接口的簡單書籍控制器,一個用于創(chuàng)建一本Book,另一個用于列出所有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方法將對具有角色的用戶可用USER,并且該POST方法將受到保護,并且只有具有該角色的用戶ADMIN才能創(chuàng)建書籍。
以上就是使用SpringBoot進行身份驗證和授權的示例詳解的詳細內(nèi)容,更多關于SpringBoot身份驗證授權的資料請關注腳本之家其它相關文章!
相關文章
java實現(xiàn)給出分數(shù)數(shù)組得到對應名次數(shù)組的方法
這篇文章主要介紹了java實現(xiàn)給出分數(shù)數(shù)組得到對應名次數(shù)組的方法,涉及java針對數(shù)組的遍歷、排序及運算的相關技巧,需要的朋友可以參考下2015-07-07
Springboot 整合 Dubbo/ZooKeeper 實現(xiàn) SOA 案例解析
這篇文章主要介紹了Springboot 整合 Dubbo/ZooKeeper 詳解 SOA 案例,需要的朋友可以參考下2017-11-11
SpringBoot詳細講解異步任務如何獲取HttpServletRequest
在使用框架日常開發(fā)中需要在controller中進行一些異步操作減少請求時間,但是發(fā)現(xiàn)在使用@Anysc注解后會出現(xiàn)Request對象無法獲取的情況,本文就此情況給出完整的解決方案2022-04-04
使用BufferedReader讀取TXT文件中數(shù)值,并輸出最大值
這篇文章主要介紹了使用BufferedReader讀取TXT文件中數(shù)值,并輸出最大值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
JUC循環(huán)屏障CyclicBarrier與CountDownLatch區(qū)別詳解
這篇文章主要為大家介紹了JUC循環(huán)屏障CyclicBarrier與CountDownLatch區(qū)別詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
解決springboot3.2集成mybatis-plus3.5.4.1報錯的問題
這篇文章給大家介紹了如何解決springboot3.2集成mybatis-plus3.5.4.1報錯的問題,文中通過圖文介紹的非常詳細,具有一定的參考價值,需要的朋友可以參考下2023-12-12
詳解SpringMVC實現(xiàn)圖片上傳以及該注意的小細節(jié)
本篇文章主要介紹了詳解SpringMVC實現(xiàn)圖片上傳以及該注意的小細節(jié),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-02-02

