使用SpringBoot進(jìn)行身份驗(yàn)證和授權(quán)的示例詳解
第一步
為了增強(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è)代表性角色:ADMIN
和USER
,該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ù)中的用戶。此外,findByLogin
Spring 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ò)展OncePerRequestFilter
spring 安全類來(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ò)安全。然后我們定義SecurityFilterChain
bean 來(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)文章
java實(shí)現(xiàn)給出分?jǐn)?shù)數(shù)組得到對(duì)應(yīng)名次數(shù)組的方法
這篇文章主要介紹了java實(shí)現(xiàn)給出分?jǐn)?shù)數(shù)組得到對(duì)應(yīng)名次數(shù)組的方法,涉及java針對(duì)數(shù)組的遍歷、排序及運(yùn)算的相關(guān)技巧,需要的朋友可以參考下2015-07-07Springboot 整合 Dubbo/ZooKeeper 實(shí)現(xiàn) SOA 案例解析
這篇文章主要介紹了Springboot 整合 Dubbo/ZooKeeper 詳解 SOA 案例,需要的朋友可以參考下2017-11-11java7 簡(jiǎn)化變參方法調(diào)用實(shí)例方法
在本篇文章里我們給大家整理的是關(guān)于java7 簡(jiǎn)化變參方法調(diào)用實(shí)例方法以及實(shí)例代碼,需要的朋友們學(xué)習(xí)下。2019-11-11SpringBoot詳細(xì)講解異步任務(wù)如何獲取HttpServletRequest
在使用框架日常開(kāi)發(fā)中需要在controller中進(jìn)行一些異步操作減少請(qǐng)求時(shí)間,但是發(fā)現(xiàn)在使用@Anysc注解后會(huì)出現(xiàn)Request對(duì)象無(wú)法獲取的情況,本文就此情況給出完整的解決方案2022-04-04使用BufferedReader讀取TXT文件中數(shù)值,并輸出最大值
這篇文章主要介紹了使用BufferedReader讀取TXT文件中數(shù)值,并輸出最大值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12JUC循環(huán)屏障CyclicBarrier與CountDownLatch區(qū)別詳解
這篇文章主要為大家介紹了JUC循環(huán)屏障CyclicBarrier與CountDownLatch區(qū)別詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12解決springboot3.2集成mybatis-plus3.5.4.1報(bào)錯(cuò)的問(wèn)題
這篇文章給大家介紹了如何解決springboot3.2集成mybatis-plus3.5.4.1報(bào)錯(cuò)的問(wèn)題,文中通過(guò)圖文介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2023-12-12詳解SpringMVC實(shí)現(xiàn)圖片上傳以及該注意的小細(xì)節(jié)
本篇文章主要介紹了詳解SpringMVC實(shí)現(xiàn)圖片上傳以及該注意的小細(xì)節(jié),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02