Spring Boot中使用 Spring Security 構建權限系統

Spring Security是一個可以爲基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組能夠在Spring應用上下文中配置的Bean,爲應用系統提供聲明式的安全訪問控制功能,減小了爲企業系統安全控制編寫大量重複代碼的工做。css

權限控制是很是常見的功能,在各類後臺管理裏權限控制更是重中之重.在Spring Boot中使用 Spring Security 構建權限系統是很是輕鬆和簡單的.下面咱們就來快速入門 Spring Security .在開始前咱們須要一對多關係的用戶角色類,一個restful的controller.html

參考項目代碼地址git

- 添加 Spring Security 依賴

首先我默認你們都已經瞭解 Spring Boot 了,在 Spring Boot 項目中添加依賴是很是簡單的.把對應的
spring-boot-starter-*** 加到pom.xml 文件中就好了github

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

- 配置 Spring Security

簡單的使用 Spring Security 只要配置三個類就完成了,分別是:web

  • UserDetails

這個接口中規定了用戶的幾個必需要有的方法spring

public interface UserDetails extends Serializable {

    //返回分配給用戶的角色列表
    Collection<? extends GrantedAuthority> getAuthorities();
    
    //返回密碼
    String getPassword();

    //返回賬號
    String getUsername();

    // 帳戶是否未過時
    boolean isAccountNonExpired();

    // 帳戶是否未鎖定
    boolean isAccountNonLocked();

    // 密碼是否未過時
    boolean isCredentialsNonExpired();

    // 帳戶是否激活
    boolean isEnabled();
}
  • UserDetailsService

這個接口只有一個方法 loadUserByUsername,是提供一種用 用戶名 查詢用戶並返回的方法。數據庫

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
  • WebSecurityConfigurerAdapter

這個內容不少,就不貼代碼了,你們能夠本身去看.segmentfault

咱們建立三個類分別繼承上述三個接口api

  • 此 User 類不是咱們的數據庫裏的用戶類,是用來安全服務的.
/**
 * Created by Yuicon on 2017/5/14.
 * https://segmentfault.com/u/yuicon
 */
public class User implements UserDetails {

    private final String id;
    //賬號,這裏是我數據庫裏的字段
    private final String account;
    //密碼
    private final String password;
    //角色集合
    private final Collection<? extends GrantedAuthority> authorities;

    User(String id, String account, String password, Collection<? extends GrantedAuthority> authorities) {
        this.id = id;
        this.account = account;
        this.password = password;
        this.authorities = authorities;
    }

    //返回分配給用戶的角色列表
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @JsonIgnore
    public String getId() {
        return id;
    }

    @JsonIgnore
    @Override
    public String getPassword() {
        return password;
    }
    
    //雖然我數據庫裏的字段是 `account`  ,這裏仍是要寫成 `getUsername()`,由於是繼承的接口
    @Override
    public String getUsername() {
        return account;
    }
    // 帳戶是否未過時
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    // 帳戶是否未鎖定
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    // 密碼是否未過時
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    // 帳戶是否激活
    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return true;
    }
}
  • 繼承 UserDetailsService
/**
 * Created by Yuicon on 2017/5/14.
 * https://segmentfault.com/u/yuicon
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    
    // jpa
    @Autowired
    private UserRepository userRepository;

    /**
     * 提供一種從用戶名能夠查到用戶並返回的方法
     * @param account 賬號
     * @return UserDetails
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
        // 這裏是數據庫裏的用戶類
        User user = userRepository.findByAccount(account);

        if (user == null) {
            throw new UsernameNotFoundException(String.format("沒有該用戶 '%s'.", account));
        } else {
            //這裏返回上面繼承了 UserDetails  接口的用戶類,爲了簡單咱們寫個工廠類
            return UserFactory.create(user);
        }
    }
}
  • UserDetails 工廠類
/**
 * Created by Yuicon on 2017/5/14.
 * https://segmentfault.com/u/yuicon
 */
final class UserFactory {

    private UserFactory() {
    }

    static User create(User user) {
        return new User(
                user.getId(),
                user.getAccount(),
                user.getPassword(),
            mapToGrantedAuthorities(user.getRoles().stream().map(Role::getName).collect(Collectors.toList()))
        );
    }
    
    //將與用戶類一對多的角色類的名稱集合轉換爲 GrantedAuthority 集合
    private static List<GrantedAuthority> mapToGrantedAuthorities(List<String> authorities) {
        return authorities.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }
}
  • 重點, 繼承 WebSecurityConfigurerAdapter 類
/**
 * Created by Yuicon on 2017/5/14.
 * https://segmentfault.com/u/yuicon
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // Spring會自動尋找實現接口的類注入,會找到咱們的 UserDetailsServiceImpl  類
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                // 設置UserDetailsService
                .userDetailsService(this.userDetailsService)
                // 使用BCrypt進行密碼的hash
                .passwordEncoder(passwordEncoder());
    }

    // 裝載BCrypt密碼編碼器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //容許跨域
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedOrigins("*")
                        .allowedMethods("GET", "HEAD", "POST","PUT", "DELETE", "OPTIONS")
                        .allowCredentials(false).maxAge(3600);
            }
        };
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 取消csrf
                .csrf().disable()
                // 基於token,因此不須要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                // 容許對於網站靜態資源的無受權訪問
                .antMatchers(
                        HttpMethod.GET,
                        "/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/webjars/**",
                        "/swagger-resources/**",
                        "/*/api-docs"
                ).permitAll()
                // 對於獲取token的rest api要容許匿名訪問
                .antMatchers("/auth/**").permitAll()
                // 除上面外的全部請求所有須要鑑權認證
                .anyRequest().authenticated();
        // 禁用緩存
        httpSecurity.headers().cacheControl();
    }
}

- 控制權限到 controller

使用 @PreAuthorize("hasRole('ADMIN')") 註解就能夠了跨域

/**
 * 在 @PreAuthorize 中咱們能夠利用內建的 SPEL 表達式:好比 'hasRole()' 來決定哪些用戶有權訪問。
 * 需注意的一點是 hasRole 表達式認爲每一個角色名字前都有一個前綴 'ROLE_'。因此這裏的 'ADMIN' 其實在
 * 數據庫中存儲的是 'ROLE_ADMIN' 。這個 @PreAuthorize 能夠修飾Controller也可修飾Controller中的方法。
 **/
@RestController
@RequestMapping("/users")
@PreAuthorize("hasRole('USER')") //有ROLE_USER權限的用戶能夠訪問
public class UserController {

   @Autowired
    private UserRepository repository;

    @PreAuthorize("hasRole('ADMIN')")//有ROLE_ADMIN權限的用戶能夠訪問
    @RequestMapping(method = RequestMethod.GET)
    public List<User> getUsers() {
        return repository.findAll();
    }
}

- 結語

Spring Boot中 Spring Security 的入門很是簡單,很快咱們就能有一個知足大部分需求的權限系統了.而配合 Spring Security 的好搭檔就是 JWT 了,二者的集成文章網絡上也不少,你們能夠自行集成.由於篇幅緣由有很多代碼省略了,須要的能夠參考參考項目代碼

相關文章
相關標籤/搜索