AdSense

網頁

2020/4/26

Spring Security 簡單角色權限範例

本篇使用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負責從資料庫取得使用者資料。在實例建構後建立預設的三名使用者adminofficerstaff,分別擁有角色ADMINOFFICERSTAFF

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


參考:

沒有留言:

AdSense