Spring Boot之整合Spring Security: 授權(quán)管理

前言

筆者學(xué)習(xí)Spring Boot有一段時(shí)間了,附上Spring Boot系列學(xué)習(xí)文章,大家有興趣可以參考參考:

  1. 5分鐘入手Spring Boot;
  2. Spring Boot數(shù)據(jù)庫(kù)交互之Spring Data JPA;
  3. Spring Boot數(shù)據(jù)庫(kù)交互之Mybatis;
  4. Spring Boot視圖技術(shù);
  5. Spring Boot之整合Swagger;
  6. Spring Boot之junit單元測(cè)試踩坑;
  7. 如何在Spring Boot中使用TestNG;
  8. Spring Boot之整合logback日志;
  9. Spring Boot之整合Spring Batch:批處理與任務(wù)調(diào)度;
  10. Spring Boot之整合Spring Security: 訪問(wèn)認(rèn)證;

在上一篇文章Spring Boot之整合Spring Security:訪問(wèn)認(rèn)證中,我們一起學(xué)習(xí)了Spring Security的訪問(wèn)認(rèn)證實(shí)現(xiàn),旨在探索如何用Spring Security進(jìn)行訪問(wèn)認(rèn)證控制,簡(jiǎn)單的說(shuō)就是:

  • 未登錄狀態(tài)下,站點(diǎn)的所有訪問(wèn)均跳轉(zhuǎn)到登錄頁(yè)面,包括API;

而這樣的操作或設(shè)置遠(yuǎn)不能代表真實(shí)場(chǎng)景,一般我們會(huì)面臨以下問(wèn)題:

1. 未登錄狀態(tài)下,訪問(wèn)API應(yīng)返回HTTP 狀碼401,并伴隨提示性response body;
2. 不同用戶需要不同的訪問(wèn)權(quán)限,即權(quán)限管理;

今天我們就來(lái)探索如何實(shí)現(xiàn)這2個(gè)需求!

項(xiàng)目代碼仍用已上傳的Git Hub倉(cāng)庫(kù),歡迎取閱:

整體步驟

  1. 準(zhǔn)備不同角色的用戶;
  2. 準(zhǔn)備測(cè)試接口;
  3. 美化登錄頁(yè)面;
  4. 授權(quán)管理配置;
  5. 驗(yàn)證授權(quán)效果;

1. 準(zhǔn)備不同角色的用戶;

1). 規(guī)范化角色;

在上一篇文章Spring Boot之整合Spring Security:訪問(wèn)認(rèn)證,我們?cè)诙嗵幨褂媒巧畔ⅲ?/p>

.roles("admin")
...
.roles("user")
...
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("admin");
...

像這種多處使用的數(shù)據(jù),應(yīng)該做個(gè)集中管理與限制,因此,我們?cè)陧?xiàng)目中創(chuàng)建constant包,創(chuàng)建一個(gè)枚舉類:UserTypeEnum,代碼如:

package com.github.dylanz666.constant;

/**
 * @author : dylanz
 * @since : 09/07/2020
 */
public enum UserTypeEnum {
    ADMIN,
    USER
}

然后把所有角色進(jìn)行重構(gòu)替換,這樣我們將角色進(jìn)行集中管理與限制,更為嚴(yán)謹(jǐn);

  • User實(shí)體類增加userType;
package com.github.dylanz666.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

import java.io.Serializable;

/**
 * @author : dylanz
 * @since : 08/31/2020
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    private String username;
    private String password;
    private String userType;
}
  • 創(chuàng)建用戶時(shí)存儲(chǔ)角色信息,查詢時(shí)也查詢出角色信息;
package com.github.dylanz666.service;

import com.github.dylanz666.constant.UserTypeEnum;
import com.github.dylanz666.domain.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author : dylanz
 * @since : 08/31/2020
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private UserDetailsImpl userService;
    @Autowired
    private UserDetails userDetails;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //Spring Security要求必須加密密碼
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        //模擬從數(shù)據(jù)庫(kù)中取出用戶信息,使用的sql如: SELECT * FROM USER WHERE USER_NAME='cherrys'
        List<User> userList = new ArrayList<>();
        User firstUser = new User();
        firstUser.setUsername("cherrys");
        firstUser.setPassword(passwordEncoder.encode("123"));
        firstUser.setUserType(UserTypeEnum.USER.toString());
        userList.add(firstUser);
        User secondUser = new User();
        secondUser.setUsername("randyh");
        secondUser.setPassword(passwordEncoder.encode("456"));
        secondUser.setUserType(UserTypeEnum.USER.toString());
        userList.add(secondUser);

        List<User> mappedUsers = userList.stream().filter(s -> s.getUsername().equals(username)).collect(Collectors.toList());

        //判斷用戶是否存在
        User user;
        if (CollectionUtils.isEmpty(mappedUsers)) {
            logger.info(String.format("The user %s is not found !", username));
            throw new UsernameNotFoundException(String.format("The user %s is not found !", username));
        }
        user = mappedUsers.get(0);
        return new UserDetailsImpl(user);
    }
}
  • 使用角色信息時(shí),均在限定范圍內(nèi):
.roles(UserTypeEnum.ADMIN.toString())
...
.roles(UserTypeEnum.UAER.toString())
...
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(this.currentUser.getUserType());
...

2. 準(zhǔn)備測(cè)試接口;

我們準(zhǔn)備4種接口,用于Demo授權(quán)管理:

  • 任何角色登錄均可訪問(wèn);
  • 無(wú)需登錄即可訪問(wèn);
  • ADMIN角色登錄方可訪問(wèn);
  • USER及比USER權(quán)限大的角色登錄方可訪問(wèn);
package com.github.dylanz666.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author : dylanz
 * @since : 08/30/2020
 */
@RestController
public class HelloController {
    @GetMapping("/hello")//任何角色登錄均可訪問(wèn);
    public String sayHello() {
        return "Hello!";
    }

    @GetMapping("/ping")//無(wú)需登錄即可訪問(wèn);
    public String ping() {
        return "Success!";
    }

    @GetMapping("/admin/hello")//ADMIN角色登錄方可訪問(wèn);
    public String adminHello() {
        return "Hello admin!";
    }

    @GetMapping("/user/hello")//USER及比USER權(quán)限大的角色登錄方可訪問(wèn);
    public String userHello() {
        return "Hello user!";
    }
}

3. 美化登錄頁(yè)面;

在上一期文章中,我們使用了自定義的登錄頁(yè)面,但樣子實(shí)在丑,有同學(xué)也許想看下,我們?nèi)绾巫约鹤鰝€(gè)美麗的登錄頁(yè)面,因此我也稍微美化了一下:

1). 在resources文件夾下創(chuàng)建static文件夾,用于放置靜態(tài)資源,如圖片、CSS文件、js文件等,我們用于放一張登錄背景圖:

登錄背景圖

2). 更新resources/templates/login.html模板文件:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Spring Security Example</title>
</head>
<body class="body">
<div class="main">
    <div class="welcome">Welcome</div>
    <hr/>
    <form th:action="@{/login}" method="post">
        <div>
            <input type="text" name="username" placeholder="username" class="input">
        </div>
        <div>
            <input type="password" name="password" placeholder="password" class="input">
        </div>
        <div th:style="'vertical-align:middle'">
            <button class="button">Sign In</button>
        </div>
    </form>
</div>
</body>
<style>
    .body {
        background-image: url("/20200907.jpg");
        background-repeat: no-repeat;
        background-position: fixed;
        background-size: cover
    }

    .welcome {
        font-size:36px;color: white;
    }

    .main {
        border:5px solid white;
        border-radius: 5px;
        width: 320px;
        height: 220px;
        margin: 120px auto;
        display: table;
        text-align: center;
        line-height: 40px;
        vertical-align: middle;
        display: table;
    }

    .button {
        margin-top: 10px;
        border: none;
        background-color: #4CAF50;
        color: white;
        padding: 12px 30px;
        text-align: center;
        text-decoration: none;
        display: inline-block;
        font-size: 14px;
        border-radius: 3px;
    }

    .input {
        margin-top: 10px;
        border: 2px solid #a1a1a1;
        background: white;
        width: 200px;
        height: 18px;
        padding: 12px 30px;
        border-radius: 5px;
        padding: 10px 28px;
        text-decoration: none;
        display: inline-block;
        font-size: 14px;
        border-radius: 3px;
    }
</style>
</html>

3). 登錄頁(yè)面效果:


登錄頁(yè)面效果

感覺(jué)漂亮多了吧!

4. 授權(quán)管理配置;

1). 自定義無(wú)權(quán)限報(bào)錯(cuò)實(shí)體類;

在授權(quán)之前,我們先自定義一個(gè)無(wú)權(quán)限報(bào)錯(cuò)實(shí)體類,定義當(dāng)無(wú)權(quán)限訪問(wèn)時(shí),告知客戶端的信息。在domain包下創(chuàng)建AuthorizationException實(shí)體類,代碼如下:

package com.github.dylanz666.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

import java.io.Serializable;

/**
 * @author : dylanz
 * @since : 09/07/2020
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
public class AuthorizationException implements Serializable {
    private static final long serialVersionUID = 1L;

    private int code;
    private String status;
    private String uri;
    private String message;

    @Override
    public String toString() {
        return "{" +
                "\"code\":\"" + code + "\"," +
                "\"status\":\"" + status + "\"," +
                "\"message\":\"" + message + "\"," +
                "\"uri\":\"" + uri + "\"" +
                "}";
    }
}
2). 開(kāi)放靜態(tài)資源訪問(wèn);

修改config包下的WebMvcConfig類,如:

package com.github.dylanz666.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author : dylanz
 * @since : 08/30/2020
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home.html").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello.html").setViewName("hello");
        registry.addViewController("/login.html").setViewName("login");
    }

    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
    }
}
3). 授權(quán)管理配置;
  • 修改WebSecurityConfig如下:
package com.github.dylanz666.config;

import com.github.dylanz666.constant.UserTypeEnum;
import com.github.dylanz666.domain.AuthorizationException;
import com.github.dylanz666.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * @author : dylanz
 * @since : 08/30/2020
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private AuthorizationException authorizationException;

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/*.jpg", "/*.png", "/*.css", "/*.js");
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                .antMatchers("/", "/home.html", "/ping").permitAll()//這3個(gè)url不用訪問(wèn)認(rèn)證
                .antMatchers("/admin/**").hasRole(UserTypeEnum.ADMIN.toString())
                .antMatchers("/user/**").hasRole(UserTypeEnum.USER.toString())
                .anyRequest()
                .authenticated()//其他url都需要訪問(wèn)認(rèn)證
                .and()
                .formLogin()
                .loginPage("/login.html")//登錄頁(yè)面的url
                .loginProcessingUrl("/login")//登錄表使用的API
                .permitAll()//login.html和login不需要訪問(wèn)認(rèn)證
                .and()
                .logout()
                .permitAll()//logout不需要訪問(wèn)認(rèn)證
                .and()
                .csrf()
                .disable()
                .exceptionHandling()
                .accessDeniedHandler(((httpServletRequest, httpServletResponse, e) -> {
                    httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    httpServletResponse.setContentType("application/json");
                    authorizationException.setCode(HttpServletResponse.SC_FORBIDDEN);
                    authorizationException.setStatus("FAIL");
                    authorizationException.setMessage("FORBIDDEN");
                    authorizationException.setUri(httpServletRequest.getRequestURI());
                    PrintWriter printWriter = httpServletResponse.getWriter();
                    printWriter.write(authorizationException.toString());
                    printWriter.flush();
                    printWriter.close();
                }))
                .authenticationEntryPoint((httpServletRequest, httpServletResponse, e) -> {
                    if (httpServletRequest.getRequestURI().equals("/hello.html")) {
                        httpServletResponse.sendRedirect("/login.html");
                        return;
                    }
                    httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    httpServletResponse.setContentType("application/json");
                    authorizationException.setCode(HttpServletResponse.SC_UNAUTHORIZED);
                    authorizationException.setStatus("FAIL");
                    authorizationException.setUri(httpServletRequest.getRequestURI());
                    authorizationException.setMessage("UNAUTHORIZED");
                    PrintWriter printWriter = httpServletResponse.getWriter();
                    printWriter.write(authorizationException.toString());
                    printWriter.flush();
                    printWriter.close();
                });
        httpSecurity.userDetailsService(userDetailsService());
        httpSecurity.userDetailsService(userDetailsService);
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        UserDetails dylanz =
                User.withUsername("dylanz")
                        .password(bCryptPasswordEncoder.encode("666"))
                        .roles(UserTypeEnum.ADMIN.toString())
                        .build();
        UserDetails ritay =
                User.withUsername("ritay")
                        .password(bCryptPasswordEncoder.encode("888"))
                        .roles(UserTypeEnum.USER.toString())
                        .build();
        UserDetails jonathanw =
                User.withUsername("jonathanw")
                        .password(bCryptPasswordEncoder.encode("999"))
                        .roles(UserTypeEnum.USER.toString())
                        .build();
        return new InMemoryUserDetailsManager(dylanz, ritay, jonathanw);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_" + UserTypeEnum.ADMIN.toString() + " > ROLE_" + UserTypeEnum.USER.toString());
        return roleHierarchy;
    }
}

我們來(lái)解讀一下:

  • "/.jpg", "/.png", "/.css", "/.js" 這幾種類型的資源訪問(wèn),均不需要認(rèn)證;
  • 對(duì)API進(jìn)行授權(quán),不同API需要不同的角色:
//以admin開(kāi)頭的API,需要ADMIN或更大權(quán)限的角色;
.antMatchers("/admin/**").hasRole(UserTypeEnum.ADMIN.toString())
//以u(píng)ser開(kāi)頭的API,需要USER或更大權(quán)限的角色;
.antMatchers("/user/**").hasRole(UserTypeEnum.USER.toString())
  • 當(dāng)權(quán)限不足時(shí),我們自定義了權(quán)限不足邏輯:
    (1). 訪問(wèn)的資源時(shí),由于權(quán)限不足,角色權(quán)限不足,則API報(bào)403,且API返回我們自定義的無(wú)權(quán)限報(bào)錯(cuò)信息;
    (2). 當(dāng)用戶訪問(wèn)資源時(shí),由于用戶未登錄,則API報(bào)401,且API返回我們自定義的無(wú)權(quán)限報(bào)錯(cuò)信息;
    (3). 當(dāng)用戶訪問(wèn)資源時(shí),權(quán)限不足且訪問(wèn)的是/hello.html頁(yè)面,則重定向到登錄頁(yè)面/login.html,這樣避免權(quán)限不足訪問(wèn)/hello.html頁(yè)面時(shí)也報(bào)401;
                .exceptionHandling()
                .accessDeniedHandler(((httpServletRequest, httpServletResponse, e) -> {                    httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    httpServletResponse.setContentType("application/json");
                    authorizationException.setCode(HttpServletResponse.SC_FORBIDDEN);
                    authorizationException.setStatus("FAIL");
                    authorizationException.setMessage("FORBIDDEN");
                    authorizationException.setUri(httpServletRequest.getRequestURI());
                    PrintWriter printWriter = httpServletResponse.getWriter();
                    printWriter.write(authorizationException.toString());
                    printWriter.flush();
                    printWriter.close();
                }))
                .authenticationEntryPoint((httpServletRequest, httpServletResponse, e) -> {
                    if (httpServletRequest.getRequestURI().equals("/hello.html")) {
                        httpServletResponse.sendRedirect("/login.html");
                        return;
                    }
                    httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    httpServletResponse.setContentType("application/json");
                    authorizationException.setCode(HttpServletResponse.SC_UNAUTHORIZED);
                    authorizationException.setStatus("FAIL");
                    authorizationException.setUri(httpServletRequest.getRequestURI());
                    authorizationException.setMessage("UNAUTHORIZED");
                    PrintWriter printWriter = httpServletResponse.getWriter();
                    printWriter.write(authorizationException.toString());
                    printWriter.flush();
                    printWriter.close();
                });
4). 角色繼承;

在實(shí)際使用場(chǎng)景中,有些角色擁有其他角色的所有權(quán)限,這時(shí),如果為每個(gè)角色都單獨(dú)創(chuàng)建完整的權(quán)限表,那么有時(shí)候會(huì)相當(dāng)冗余。
因此,這時(shí)候我們就要用到Spirng Security中的角色繼承;
在WebSecurityConfig類中,我寫了一個(gè)角色繼承的例子:

@Bean
public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
    roleHierarchy.setHierarchy("ROLE_" + UserTypeEnum.ADMIN.toString() + " > ROLE_" + UserTypeEnum.USER.toString());
    return roleHierarchy;
}

即:USER繼承于ADMIN角色,USER角色只擁有ADMIN的部分功能,而ADMIN擁有USER角色的所有功能;
注意角色繼承的寫法,每個(gè)角色前要加ROLE_,繼承時(shí)用 > 符號(hào)連接,符號(hào)左邊權(quán)限大,符號(hào)右邊權(quán)限小。

5). 資源多角色訪問(wèn)配置;

假設(shè)我們有些/any開(kāi)頭的API,可以給多個(gè)角色使用,如SUPERVISOR角色(假設(shè)有這個(gè)角色)和USER角色,我們可以在WebSecurityConfig中這么配置:

.antMatchers("/any/**").hasAnyRole(UserTypeEnum.SUPERVISOR.toString(), UserTypeEnum.USER.toString())

5. 驗(yàn)證授權(quán)效果;

啟動(dòng)項(xiàng)目:


啟動(dòng)項(xiàng)目

開(kāi)始驗(yàn)證:

1). 訪問(wèn)不用訪問(wèn)認(rèn)證的API;
訪問(wèn)不用訪問(wèn)認(rèn)證的API
2). 訪問(wèn)需要任意角色通過(guò)訪問(wèn)認(rèn)證的API;
  • 登錄前:


    需任意角色認(rèn)證API,登錄前
  • 登錄后:

需任意角色認(rèn)證API,登錄后
3). 訪問(wèn)需要ADMIN角色通過(guò)訪問(wèn)認(rèn)證的API;
  • 登錄前:


    需要ADMIN角色認(rèn)證API,登錄前
  • 登錄后:


    需要ADMIN角色認(rèn)證API,登錄后
4). 訪問(wèn)需要USER角色通過(guò)訪問(wèn)認(rèn)證的API;
  • 登錄前:


    需要USER角色認(rèn)證API,登錄前
  • 登錄后:


    需要USER角色認(rèn)證API,登錄后
  • 訪問(wèn)權(quán)限外API:(注意,此時(shí)的API status code為403,可以從瀏覽器的Network中查看)


    訪問(wèn)權(quán)限外API
5). 訪問(wèn)不存在的API;

我沒(méi)有額外定制不存在的錯(cuò)誤信息或錯(cuò)誤頁(yè)面,默認(rèn)為:


訪問(wèn)不存在的API

Controller中授權(quán)管理

除了上述在WebSecurityConfig統(tǒng)一對(duì)API進(jìn)行授權(quán),我們還可以在項(xiàng)目的Controller中進(jìn)行授權(quán)管理,步驟:

1. WebSecurityConfig類添加注解:@EnableGlobalMethodSecurity(prePostEnabled = true),如:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
2. Controller內(nèi)增加另外的API,并在API中配置權(quán)限,而不是在WebSecurityConfig中配置:
    @GetMapping("/controller/hello")
    @PreAuthorize(value="isAuthenticated()")//任何角色登錄均可訪問(wèn);
    public String controllerAnyHello() {
        return "Hello controller any!";
    }

    @GetMapping("/controller/admin/hello")
    @PreAuthorize("hasRole('ADMIN')")//ADMIN角色登錄方可訪問(wèn);
    public String controllerAdminHello() {
        return "Hello controller admin!";
    }

    @GetMapping("/controller/both/hello")
    @PreAuthorize("hasAnyRole('ADMIN', 'USER')")//ADMIN或USER角色登錄方可訪問(wèn);
    public String controllerBothHello() {
        return "Hello controller both!";
    }

簡(jiǎn)單分析一下:

1). 使用@EnableGlobalMethodSecurity注解后,Controller中的@PreAuthorize方可生效;
2). @PreAuthorize注解內(nèi)可以指定權(quán)限,如:

  • @PreAuthorize(value="isAuthenticated()"),代表//任何角色登錄均可訪問(wèn);
  • @PreAuthorize("hasRole('ADMIN')"),代表ADMIN角色登錄方可訪問(wèn);
  • @PreAuthorize("hasAnyRole('ADMIN', 'USER')"),可用hasAnyRole為指定的多個(gè)角色進(jìn)行授權(quán);
  • 這種注解方式,hasRole和hasAnyRole內(nèi)的角色不能引用UserTypeEnum內(nèi)的值,只能手填hard code;

效果:

與在WebSecurityConfig配置的效果是一樣的,但該方式可對(duì)每個(gè)API進(jìn)行單獨(dú)配置,不會(huì)導(dǎo)致WebSecurityConfig配置在復(fù)雜應(yīng)用里頭的配置很長(zhǎng),并且對(duì)開(kāi)發(fā)人員更加直觀,授權(quán)管理也更加靈活;

總結(jié)

至此,我們學(xué)會(huì)了對(duì)API、資源進(jìn)行授權(quán)管理,若結(jié)合之前學(xué)的訪問(wèn)認(rèn)證,則我們已能夠?qū)?yīng)用進(jìn)行靈活的訪問(wèn)控制和權(quán)限控制,可以滿足大部分認(rèn)證授權(quán)場(chǎng)景!

如果本文對(duì)您有幫助,麻煩點(diǎn)贊+關(guān)注!

謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。