本篇使用Spring Security搭配Basic Authentication驗證,簡單的使用者角色(role)權限與JSR-250 @RoleAllowed
來做REST API的存取限制。
範例環境:
- Java 11
- Maven
- Spring Boot 2.2.6.RELEASE
- Spring Web
- Spring Security
- Spring Data JPA
- H2 Database
- Lombok
因為Spring constructor依賴注入搭配Lombok的@AllArgsConstructor
,所以類別的成員變數不用@Autowired
即可取得Bean的實例。
Spring Security配置類DemoWebSecurityConfig
。使用Basic Authentication驗證,使用自訂UserDetailsService
取得使用者資訊。啟用@RoleAllowed
。
DemoWebSecurityConfig
package com.abc.demo3.config.security;
import lombok.AllArgsConstructor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
@AllArgsConstructor
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@EnableWebSecurity
public class DemoWebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
// .permitAll()
.authenticated()
.and().formLogin().disable()
.httpBasic()
.and().csrf().disable();
}
}
UserService
實作UserDetailsService
。覆寫loadUserByUsername()
方法來委託UserDao
從資料庫取得使用者資訊。
UserService
package com.abc.demo3.service;
import com.abc.demo3.dao.UserDao;
import lombok.AllArgsConstructor;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
@AllArgsConstructor
@Service
public class UserService implements UserDetailsService {
private final UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userDao.getByUsername(username)
.map(e -> User.withUsername(e.getUsername())
.password("{noop}" + e.getPassword())
.roles(e.getRole())
.build()).orElse(null);
}
public List<String> getAllUserNames() {
return userDao.getAllUserNames();
}
}
UserDao
負責從資料庫取得使用者資料。在實例建構後建立預設的三名使用者admin
、officer
、staff
,分別擁有角色ADMIN
、OFFICER
、STAFF
。
UserDao
package com.abc.demo3.dao;
import com.abc.demo3.entity.User;
import com.abc.demo3.repo.UserRepo;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@AllArgsConstructor
@Service
public class UserDao {
private final UserRepo userRepo;
@PostConstruct
void init(){
List<User> userList = List.of(
User.builder()
.username("admin")
.password("123")
.role("ADMIN").build(),
User.builder()
.username("officer")
.password("123")
.role("OFFICER").build(),
User.builder()
.username("staff")
.password("123")
.role("STAFF").build());
userRepo.saveAll(userList);
}
public List<String> getAllUserNames() {
return userRepo.findAll().stream()
.map(User::getUsername)
.collect(Collectors.toList());
}
public Optional<User> getByUsername(String username) {
return userRepo.findByUsername(username);
}
}
Spring Data JPA負責存取User
實體的UserRepo
介面。使用方法名稱查詢。
UserRepo
package com.abc.demo3.repo;
import com.abc.demo3.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepo extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
與資料表USER
映設的實體User
。不過這邊使用H2 in-memory database來模擬資料庫。
User
package com.abc.demo3.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class User {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
private String password;
private String role;
}
本篇的使用者角色單純,一位使用者只會有一種角色,所以角色的權限是維護在同張USER
資料表如下。
+----+----------+----------+---------+
| ID | USERNAME | PASSWORD | ROLE |
+----+----------+----------+---------+
| 1 | admin | ******** | ADMIN |
+----+----------+----------+---------+
| 2 | officer | ******** | OFFICER |
+----+----------+----------+---------+
| 3 | staff | ******** | STAFF |
+----+----------+----------+---------+
| 4 | john | ******** | STAFF |
+----+----------+----------+---------+
| 5 | mary | ******** | OFFICER |
+----+----------+----------+---------+
| 6 | steve | ******** | STAFF |
+----+----------+----------+---------+
ActionController
為用來被測試的API Controller。
ActionController
package com.abc.demo3.controller;
import org.springframework.web.bind.annotation.*;
import javax.annotation.security.RolesAllowed;
@RestController
@RequestMapping("/action")
public class ActionController {
@RolesAllowed({"STAFF", "OFFICER", "ADMIN"})
@GetMapping("/get")
public String get() {
String message = "get";
System.out.println(message);
return message;
}
@RolesAllowed({"OFFICER", "ADMIN"})
@PostMapping("/add")
public String add() {
String message = "add";
System.out.println(message);
return message;
}
@RolesAllowed({"OFFICER", "ADMIN"})
@PatchMapping("/update")
public String update() {
String message = "update";
System.out.println(message);
return message;
}
@RolesAllowed({"ADMIN"})
@DeleteMapping("/delete")
public String delete() {
String message = "delete";
System.out.println(message);
return message;
}
}
完整範例請見github。
參考:
沒有留言:
張貼留言