springSecurity非前后端分離配置(生產(chǎn)環(huán)境可用)

1 場景

1.1 說明

springSecurity作為一個權(quán)限管理系統(tǒng),在生產(chǎn)環(huán)境使用绒净,還是比較復(fù)雜的见咒,涉及的相關(guān)點比較多。網(wǎng)上文章里疯溺,并沒有比較全的配置论颅。

本文主要將springSecurity在生產(chǎn)環(huán)境中使用時,需要注意到的地方囱嫩,進行了相關(guān)整理恃疯。

springSecurity默認是基于session的非前后端分離場景,本文基于此場景進行配置墨闲,有時間的時候今妄,后續(xù)會記錄補充如下場景:
(1)非前后端分離
(2)基于JWT的非前后端分離
(3)網(wǎng)關(guān)上整合權(quán)限控制

1.2 源碼

本文是基于各個應(yīng)用模塊單獨寫的配置,完整的測試代碼鸳碧,可關(guān)注盾鳞、點贊后私信博主。所有的代碼瞻离,博主均已校驗過腾仅,demo可正常跑通,可直接應(yīng)用到生產(chǎn)環(huán)境中套利。

1.3 版本

spring-boot版本:2.3.3.RELEASE

其他版本:

<!-- ==========【freemarker權(quán)限security標(biāo)簽支持】========== start -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>5.3.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    <scope>provided</scope>
</dependency>
<!-- ==========【freemarker權(quán)限security標(biāo)簽支持】========== end -->

2 登錄

前提推励,屏蔽csrf防護鹤耍,否則系統(tǒng)登錄、登出無法正常訪問验辞。如需打開稿黄,需自己進行相關(guān)配置。

// 屏蔽csrf防護
http.csrf().disable();

2.1 自定義登錄頁面

本文前端頁面跌造,使用的freemarker杆怕。

2.1.1 頁面配置

springSecurity登錄表單默認使用的是自己的頁面,一般系統(tǒng)都需要進行自定義登錄頁面壳贪。

訪問系統(tǒng)頁面時陵珍,如未認證,則自動跳轉(zhuǎn)到登錄頁面撑碴。

文件路徑:resources\templates\system\main.ftlh

如下:

<form action="/doLogin" method="post">
    <table>
        <tr>
            <td>用戶名:</td>
            <td><input type="text" name="username" value="admin"></td>
        </tr>
        <tr>
            <td>密碼:</td>
            <td><input type="text" name="password" value="123456"></td>
        </tr>
        <tr>
            <td><input type="submit" value="登錄"></td>
        </tr>
    </table>
</form>

如上代碼所示撑教,登錄時的相關(guān)參數(shù)如下:

參數(shù)描述 參數(shù)
加載登錄頁面路徑 /initLogin
提交登錄請求路徑 /doLogin
用戶名 username
密碼 password
2.1.2 后臺代碼配置
/**
  * 加載登錄頁面
  * @return
  */
@RequestMapping(value = {"initLogin"})
public ModelAndView initLogin(HttpServletRequest request) {
    ModelAndView modelAndView = new ModelAndView("system/login");
    return modelAndView;
}

備注:提交登錄請求路徑(/doLogin)無需配置朝墩,此請求醉拓,會走security自己的認證流程。

2.1.3 登錄請求放行

放行“登錄頁面”收苏,“登錄請求”等相關(guān)權(quán)限驗證請求

http.authorizeRequests()
    // ......
    // 放行“登錄頁面”亿卤,“登錄請求”,“退出”等相關(guān)權(quán)限驗證請求
    .antMatchers("/initLogin", "/doLogin", "/doLogout").permitAll()
    // 任意請求需認證通過
    .anyRequest().authenticated();
2.1.4 登錄參數(shù)配置

security的登錄請求路徑和參數(shù)都是默認配置的鹿霸,這里我們更改為自己的請求配置:

http.formLogin()
    // 登錄時自定義“用戶”參數(shù)名(默認為:username)
    .usernameParameter("username")
    // 登錄時自定義“用戶”參數(shù)名(默認為:password)
    .passwordParameter("password")
    // 自定義登錄頁面(默認為:login/GET)
    .loginPage("/initLogin")
    // 自定義登錄請求路徑(默認為:login/POST)
    .loginProcessingUrl("/doLogin")

2.2 登錄校驗邏輯

2.1 自定義登錄用戶對象

擴展security自帶的用戶對象(org.springframework.security.core.userdetails.User)排吴,擴展自定義屬性

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;

/**
 * 自定義用戶對象
 */
public class LoginUser extends User {
    
    /**
     * 自定義用戶屬性
     */
    private String departmentCode;
    
    public LoginUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }
    
    public String getDepartmentCode() {
        return departmentCode;
    }
    
    public void setDepartmentCode(String departmentCode) {
        this.departmentCode = departmentCode;
    }
}
2.2 自定義用戶認證service

自定義用戶認證,主要是將用戶根據(jù)用戶名懦鼠,從數(shù)據(jù)庫中查詢出來钻哩。組裝好org.springframework.security.core.userdetails.UserDetails的實現(xiàn)類對象,交由security進行驗證肛冶。后續(xù)security的驗證街氢,如未拋出異常,則認證通過睦袖,否則認證失敗珊肃,然后,可根據(jù)拋出的異常類型馅笙,來識別認證失敗的原因伦乔。

/**
 * 自定義用戶認證service
 */
@Component
public class CustomUserDetailsService implements UserDetailsService {
    
    @Resource
    private UserService userService;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // ========== 【1】校驗參數(shù) ========== 
        if (StringUtils.isBlank(username)) {
            throw new UsernameNotFoundException("用戶代碼為空");
        }
        
        // ========== 【2】查詢用戶信息 ========== 
        UserInfo userInfo = userService.getUserInfoByUserName(username);
        if (userInfo == null) {
            throw new UsernameNotFoundException("用戶名或密碼錯誤");
        }
        // 密碼
        String password = userInfo.getPassword();
        // 用戶其他信息(用戶部門代碼)
        String departmentCode = userInfo.getDepartmentCode();
        
        // ========== 【3】查詢授權(quán)信息 ========== 
        // 角色代碼和授權(quán)代碼,均在此列表中配置(角色代碼前加“ROLE_”董习,來和權(quán)限區(qū)分)
        List<String> authorityList = new ArrayList<>();
        // 初始化角色信息
        List<String> roleCodeList = userService.getRoleCodeListByUserName(username);
        if (CollectionUtils.isNotEmpty(roleCodeList)) {
            for (String roleCode : roleCodeList) {
                authorityList.add("ROLE_" + roleCode);
            }
        }
        // 初始化權(quán)限信息
        List<String> permissionCodeList = userService.getPermissionCodeListByUserName(username);
        if (CollectionUtils.isNotEmpty(permissionCodeList)) {
            authorityList.addAll(permissionCodeList);
        }
        
        // ========== 【4】組裝用戶信息 ==========
        // 組裝通用信息
        LoginUser loginUser = new LoginUser(username, password, AuthorityUtils.createAuthorityList(authorityList.toArray(new String[0])));
        // 組裝自定義用戶信息
        loginUser.setDepartmentCode(departmentCode);
        
        return loginUser;
    }
}

2.3 認證通過邏輯

認證通過后烈和,需進行頁面跳轉(zhuǎn),有兩種方式皿淋,一種是跳轉(zhuǎn)到訪問登錄頁面前的頁面(訪問某個頁面招刹,因為未認證虱颗,自動跳轉(zhuǎn)到登錄頁面,當(dāng)?shù)卿洺晒笳嵛梗詣犹D(zhuǎn)到此頁面忘渔,而不是系統(tǒng)主頁面),一種是跳轉(zhuǎn)到系統(tǒng)主頁面缰儿。

可根據(jù)實際業(yè)務(wù)需求選擇畦粮,這里選擇第一種。

一般登錄成功后乖阵,系統(tǒng)會執(zhí)行自定義邏輯宣赔,如記錄登錄IP、登錄時間等,這里使用自定義登錄成功邏輯莫鸭。

2.3.1 自定義登錄成功處理器
/**
 * 自定義登錄成功處理器
 */
@Component
public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        // TODO 自定義登錄成功邏輯......
        System.out.println("自定義登錄成功邏輯......");
        super.onAuthenticationSuccess(request, response, authentication);
    }
}
2.3.2 配置登錄成功邏輯
  • 注入bean
/**
  * 自定義登錄成功邏輯
 */
@Autowired
private CustomLoginSuccessHandler customLoginSuccessHandler;
  • 配置執(zhí)行器
http.formLogin()
    // 自定義登錄成功forward路徑
    //.successForwardUrl("/")
    // 自定義登錄成功redirect路徑【默認】(登錄成功后粘勒,頁面重定向到跳轉(zhuǎn)登錄頁面前的頁面Referer)
    //.defaultSuccessUrl("/")
    // 自定義登錄成功邏輯(redirect主頁面+自定義業(yè)務(wù)邏輯)
    .successHandler(customLoginSuccessHandler)
    // 自定義登錄成功redirect路徑(登錄成功后,頁面重定向到設(shè)置的登錄成功頁面:"/")
    //.defaultSuccessUrl("/", true)
2.3.3 登錄主頁面后臺代碼
/**
  * 系統(tǒng)主頁面
  * @return
  */
@RequestMapping(value = {"/"})
public ModelAndView main() {
    ModelAndView modelAndView = new ModelAndView("system/main");

    // 獲取當(dāng)前登錄人信息
    LoginUser loginUser = SecurityUtils.getLoginUser();
    modelAndView.addObject("loginUser", loginUser);

    return modelAndView;
}

2.4 獲取認證信息

security認證通過后钩蚊,會將認證信息,保存在ThreadLocal中蹈矮,故可以通過其自帶的靜態(tài)方法獲扰槁摺:

SecurityContextHolder.getContext().getAuthentication()

此處可獲取2.2中封裝的自定義對象:LoginUser。

/**
 * security工具類
 */
public class SecurityUtils {
    
    /**
     * 默認角色前綴
     */
    private static final String DEFAULT_ROLE_PREFIX = "ROLE_";
    
    /**
     * 獲取認證信息
     * @return
     */
    public static Authentication getAuthentication() {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        if (securityContext != null) {
            return securityContext.getAuthentication();
        }
        return null;
    }
    
    /**
     * 獲取當(dāng)前登錄用戶對象
     * @return
     */
    public static LoginUser getLoginUser() {
        Authentication authentication = SecurityUtils.getAuthentication();
        if (authentication != null) {
            return (LoginUser) authentication.getPrincipal();
        }
        return null;
    }
}

2.5 認證失敗邏輯

認證失敗時泛鸟,跳轉(zhuǎn)到登錄頁面蝠咆,并返回錯誤信息。

這里我們通過forward到認證失敗頁面(即登錄頁面)北滥,由于使用的是forward進行的跳轉(zhuǎn)刚操,故可以獲取request中的屬性WebAttributes.AUTHENTICATION_EXCEPTION,來獲取異常信息再芋,來返回到前臺菊霜。

也可通過redirect到認證失敗頁面(登錄頁面),但是請求中無法獲取失敗異常信息WebAttributes.AUTHENTICATION_EXCEPTION祝闻。
可以考慮自定義認證失敗邏輯failureHandler占卧,來實現(xiàn)此功能。

2.5.1 認證失敗后臺
/**
  * 登錄失敗頁面
  * @param request
  * @return
  */
@RequestMapping(value = {"loginFail"})
public ModelAndView loginFail(HttpServletRequest request) {
    ModelAndView modelAndView = new ModelAndView("system/login");
    String error = null;
    // 登錄異常處理
    Object exception = request.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    if (exception instanceof AuthenticationException) {
        if(exception instanceof UsernameNotFoundException){
            // 自己拋出異常信息
            error = ((UsernameNotFoundException) exception).getMessage();
        }else if (exception instanceof BadCredentialsException){
            error = "用戶名或者密碼輸入錯誤联喘,請重新輸入!";
        }else if (exception instanceof LockedException){
            error = "賬戶被鎖定华蜒,請聯(lián)系管理員!";
        }else if (exception instanceof CredentialsExpiredException){
            error = "密碼過期,請聯(lián)系管理員!";
        }else if (exception instanceof AccountExpiredException){
            error = "賬戶過期豁遭,請聯(lián)系管理員!";
        }else if (exception instanceof DisabledException){
            error = "賬戶被禁用叭喜,請聯(lián)系管理員!";
        }else{
            error = "認證失敗";
        }
    }
    modelAndView.addObject("error", error);
    return modelAndView;
}
2.5.2 認證失敗配置
http.formLogin()
    // 自定義失敗forward路徑(默認為:loginPage + "?error")
    .failureForwardUrl("/loginFail")

2 登出

2.1 頁面配置

<a href="/doLogout">退出</a>

2.2 后臺代碼

登出,走的是security的邏輯蓖谢,無需自己寫后臺代碼捂蕴。

2.3 登出請求放行

有可能執(zhí)行登出操作的時候譬涡,session已失效,因此登出系統(tǒng)也需要放行請求啥辨,不進行認證校驗

http.authorizeRequests()
    // ......
    // 放行“登錄頁面”涡匀,“登錄請求”,“退出”等相關(guān)權(quán)限驗證請求
    .antMatchers("/initLogin", "/doLogin", "/doLogout").permitAll()
    // 任意請求需認證通過
    .anyRequest().authenticated();

2.4 自定義登出處理器

一般系統(tǒng)登出時溉知,也會進行相關(guān)業(yè)務(wù)操作陨瘩,如記錄日志,發(fā)送消息等级乍。

2.4.1 自定義登出成功處理器
/**
 * 自定義登出成功處理器
 **/
@Configuration
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // TODO 自定義登出成功邏輯......
        System.out.println("自定義登出成功邏輯......");
        super.onLogoutSuccess(request, response, authentication);
    }
}
2.4.2 配置登錄成功邏輯
  • 注入bean
/**
  * 自定義登出成功邏輯
  */
@Autowired
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
  • 配置執(zhí)行器
// ---------- [登出系統(tǒng)] ----------
http.logout()
    // 刪除認證信息(默認為true)
    .clearAuthentication(true)
    // 退出系統(tǒng)時使session無效(默認為true)
    .invalidateHttpSession(true)
    // 退出系統(tǒng)時舌劳,自定義請求路徑
    .logoutUrl("/doLogout")
    // 自定義登出成功邏輯(redirect登錄頁面+自定義業(yè)務(wù)邏輯)
    .logoutSuccessHandler(customLogoutSuccessHandler);

3 URL授權(quán)驗證

security可以對指定URL進行授權(quán)驗證判斷。個人認為主要是用來對某些特殊訪問請求進行專門的認證(如api)玫荣,一般不會在此配置角色權(quán)限的驗證甚淡。

3.1 驗證配置
// ---------- [基于表單的身份驗證] ----------
// 順序很重要,從上而下依次驗證
http.authorizeRequests()
    // ---------- 自定義基于URL授權(quán)驗證[也可加http方式限制:(HttpMethod method, String... antPatterns)]
    // 判斷是否有某權(quán)限
    .antMatchers("/noPower/**").hasAuthority("noPower")
    // 判斷是否有某角色
    .antMatchers("/admin/**").hasRole("admin")
    // 判斷是否有任一權(quán)限
    .antMatchers("/user/list").hasAnyAuthority("user:list", "user:all")
    // 自定義權(quán)限校驗(參數(shù)來自WebSecurityExpressionRoot屬性)
    .antMatchers("/api/**").access("@customAccessForApi.hasPermission(request,authentication)")
    // 放行“登錄”捅厂,“退出”等相關(guān)權(quán)限驗證請求
    .antMatchers("/initLogin", "/doLogin", "/doLogout").permitAll()
    // 任意請求需認證通過
    .anyRequest().authenticated();

認證通過對請求進行攔截贯卦,如antMatchers,可以使用多種認證方式:

  • 使用hasRole對角色進行認證

  • 可以使用hasAuthority對權(quán)限認證恒傻。

  • 通過自定義邏輯對請求進行進行認證

3.2 自定義邏輯驗證

3.2.1 自定義驗證邏輯
/**
 * 自定義權(quán)限監(jiān)測
 */
@Component
public class CustomAccessForApi {
    /**
     * 權(quán)限監(jiān)測
     */
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        if (StringUtils.startsWith(request.getRemoteAddr(), "192.168.56")) {
            // 滿足條件的IP脸侥,可以訪問此接口
            return true;
        }
        return false;
    }
}
3.2.2 配置自定義驗證邏輯
 http.authorizeRequests()
     // 自定義權(quán)限校驗(參數(shù)來自WebSecurityExpressionRoot屬性)
     .antMatchers("/api/**").access("@customAccessForApi.hasPermission(request,authentication)")

4 方法授權(quán)驗證

security可以對具體某個方法進行授權(quán)驗證判斷,一般加在Controller的對外請求方法上盈厘。

此方法,需要配置注解@EnableGlobalMethodSecurity開啟方法前后權(quán)限判斷官边,一般使用@PreAuthorize進行方法執(zhí)行前判斷

// 開啟全局“方法安全”控制(開啟方法前后權(quán)限判斷沸手,一般使用@PreAuthorize進行方法執(zhí)行前判斷)
@EnableGlobalMethodSecurity(prePostEnabled = true)

4.1 判斷是否有角色

@PreAuthorize("hasRole('admin')")
@ResponseBody
@RequestMapping("add")
public String add() {
    return "department add ...";
}

4.2 判斷是否有權(quán)限

@PreAuthorize("hasAuthority('department:list')")
@RequestMapping("list")
public ModelAndView list() {
    return new ModelAndView("department/department_list");
}

4.3 自定義權(quán)限判斷

4.3.1 自定義校驗器
@Configuration
public class CustomAccessForDepartmentDelete {
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        return false;
    }
}
4.3.2 配置自定義校驗器
// 自定義權(quán)限控制,參數(shù)前需加#
@PreAuthorize("@customAccessForDepartmentDelete.hasPermission(#request,#authentication)")
@ResponseBody
@RequestMapping("delete")
public String delete() {
    return "department delete ...";
}

5 前端頁面授權(quán)驗證

security也可以在前端頁面注簿,加標(biāo)簽契吉,來控制頁面元素的展示。這里前端使用的是freemarker诡渴,需要額外做些配置捐晶,才可以使用。

5.1 freemarker配置security標(biāo)簽

5.1.1 maven依賴
<!-- ==========【freemarker權(quán)限security標(biāo)簽支持】========== start -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>5.3.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    <scope>provided</scope>
</dependency>
<!-- ==========【freemarker權(quán)限security標(biāo)簽支持】========== end -->
5.1.2 拷貝tld文件

spring-security-taglibs/META-INF/security.tld妄辩,拷貝到拷貝到resource的目錄tags中惑灵。

5.1.3 配置
/**
 * Freemarker的Security標(biāo)簽支持
 */
@Configuration
public class FreemarkerSecurityTaglibConfig{
    
    /**
     * security標(biāo)簽路徑(來自"spring-security-taglibs/META-INF/security.tld")<br>
     * 此文件需拷貝到resource的目錄tags中
     */
    private static final String SECURITY_TLD_PATH="/tags/security.tld";
    
    @Autowired
    private FreeMarkerConfigurer freeMarkerConfigurer;
    
    @PostConstruct
    public void freeMarkerConfigurer() {
        List<String> classpathTlds = new ArrayList<>();
        classpathTlds.add(SECURITY_TLD_PATH);
        freeMarkerConfigurer.getTaglibFactory().setClasspathTlds(classpathTlds);
    }
}

5.2 引入標(biāo)簽

freemarker前臺文件ftlh,文件頭眼耀,引入標(biāo)簽:

<#assign security=JspTaglibs["http://www.springframework.org/security/tags"] />

5.3 使用

5.3.1 判斷有無角色
<@security.authorize access="hasRole('admin')">
    <a href="/department/test">test按鈕</a>
</@security.authorize>
5.3.2 判斷有無權(quán)限
<@security.authorize access="hasAuthority('department:test')">
    <a href="/department/test">test按鈕</a>
</@security.authorize>

6 api驗證授權(quán)

有時候英支,我們希望在java代碼中直接判斷有無某角色、有無某權(quán)限哮伟。作者對此進行了代碼封裝干花,可通過靜態(tài)方法進行判斷妄帘。

此判斷,暫不支持權(quán)限繼承池凄。

6.1 封裝

/**
 * security工具類
 */
public class SecurityUtils {
    
    /**
     * 默認角色前綴
     */
    private static final String DEFAULT_ROLE_PREFIX = "ROLE_";
    
    /**
     * 獲取認證信息
     * @return
     */
    public static Authentication getAuthentication() {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        if (securityContext != null) {
            return securityContext.getAuthentication();
        }
        return null;
    }
    
    /**
     * 判斷有某權(quán)限(暫不支持權(quán)限繼承)
     * @return
     */
    public static boolean hasAuthority(String authority) {
        if (StringUtils.isNotEmpty(authority)) {
            return SecurityUtils.hasAnyAuthorityName(null, authority);
        }
        return false;
    }
    
    /**
     * 判斷有任一權(quán)限(暫不支持權(quán)限繼承)
     * @param authorityArr
     * @return
     */
    public static boolean hasAnyAuthority(String... authorityArr) {
        if (authorityArr != null && authorityArr.length > 0) {
            return SecurityUtils.hasAnyAuthorityName(null, authorityArr);
        }
        return false;
    }
    
    /**
     * 判斷有某角色(暫不支持權(quán)限繼承)
     * @param role
     * @return
     */
    public static boolean hasRole(String role) {
        if (StringUtils.isNotEmpty(role)) {
            return SecurityUtils.hasAnyAuthorityName(DEFAULT_ROLE_PREFIX, role);
        }
        return false;
    }
    
    /**
     * 判斷有任一角色(暫不支持權(quán)限繼承)
     * @param roleArr
     * @return
     */
    public static boolean hasAnyRole(String... roleArr) {
        if (roleArr != null && roleArr.length > 0) {
            return SecurityUtils.hasAnyAuthorityName(DEFAULT_ROLE_PREFIX, roleArr);
        }
        return false;
    }
    
    /**
     * 判斷是否滿足通用權(quán)限信息抡驼,有一個滿足即為滿足(包括角色和權(quán)限)
     * @param prefix         前綴
     * @param authorityNames 權(quán)限名稱
     * @return
     */
    private static boolean hasAnyAuthorityName(String prefix, String... authorityNames) {
        if (authorityNames != null && authorityNames.length > 0) {
            Set<String> authoritySet = SecurityUtils.getAuthoritySet();
            if (CollectionUtils.isNotEmpty(authoritySet)) {
                for (String authorityName : authorityNames) {
                    String defaultedRole = getRoleWithDefaultPrefix(prefix, authorityName);
                    if (authoritySet.contains(defaultedRole)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
    
    /**
     * 獲取當(dāng)前用戶權(quán)限集合信息
     * @return
     */
    private static Set<String> getAuthoritySet() {
        Authentication authentication = SecurityUtils.getAuthentication();
        if (authentication != null) {
            Collection<? extends GrantedAuthority> userAuthorities = authentication.getAuthorities();
            return AuthorityUtils.authorityListToSet(userAuthorities);
        }
        return null;
    }
    
    /**
     * 如果defaultRolePrefix為非空且role的開頭不是defaultRolePrefix,則使用defaultRolePrefix前綴角色肿仑。
     * @param defaultRolePrefix
     * @param role
     * @return
     */
    private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
        if (role == null) {
            return role;
        }
        if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) {
            return role;
        }
        if (role.startsWith(defaultRolePrefix)) {
            return role;
        }
        return defaultRolePrefix + role;
    }
}

6.2 使用

java代碼中直接使用靜態(tài)方法判斷即可婶恼,如下:

// 判斷有角色
SecurityUtils.hasRole("admin");
// 判斷有任一角色
SecurityUtils.hasAnyRole("admin","user");
// 判斷有權(quán)限
SecurityUtils.hasAuthority("user:add");
// 判斷有任一權(quán)限
SecurityUtils.hasAnyAuthority("user:add","user:edit");

7 自定義授權(quán)驗證失敗返回方式

當(dāng)系統(tǒng)訪問認知失敗時,默認返回授權(quán)驗證的錯誤頁面柏副,這種方式對于ajax的請求勾邦,十分不友好。

因此我們希望在驗證授權(quán)失敗時割择,如果是json請求眷篇,則返回json格式的錯誤信息,如果是其他請求荔泳,則返回錯誤頁面蕉饼。

7.1 前臺頁面

授權(quán)驗證失敗頁面accessDenied.ftlh

<body>
無訪問權(quán)限
</body>

7.2 后臺代碼

/**
  * 無訪問權(quán)限頁面
  * @return
  */
@RequestMapping("accessDenied")
public ModelAndView accessDenied() {
    return new ModelAndView("system/accessDenied");
}

7.3 配置

// ---------- [異常處理]ExceptionTranslationFilter ----------
http.exceptionHandling()
    // 認證失敗(不使用默認的表單form登錄認證時玛歌,可使用此方式)
    //.authenticationEntryPoint((request, response, authException) -> {
    //})
    // 訪問拒絕句柄(認證通過后昧港,無操作權(quán)限時)
    .accessDeniedHandler((request, response, accessDeniedException) -> {
        String contentType = request.getHeader("content-type");
        boolean jsonRequestFlag = (contentType != null && contentType.contains("json"));
        // 判斷是否是json請求
        if (jsonRequestFlag) {
            response.setStatus(HttpStatus.FORBIDDEN.value());
            response.setContentType("application/json;charset=UTF-8");
            Writer writer = response.getWriter();
            JSONObject json = new JSONObject();
            json.put("success", false);
            json.put("message", "無操作權(quán)限:" + accessDeniedException.getMessage());
            writer.write(json.toJSONString());
            writer.flush();
            writer.close();
        } else {
            request.getRequestDispatcher("/accessDenied").forward(request, response);
        }
    });

8 分布式session共享

可使用spring-session或tomcat-redis-session-manager。后續(xù)補充

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載支子,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者创肥。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市值朋,隨后出現(xiàn)的幾起案子叹侄,更是在濱河造成了極大的恐慌,老刑警劉巖昨登,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趾代,死亡現(xiàn)場離奇詭異,居然都是意外死亡丰辣,警方通過查閱死者的電腦和手機撒强,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笙什,“玉大人飘哨,你說我怎么就攤上這事〉孟妫” “怎么了杖玲?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長淘正。 經(jīng)常有香客問我摆马,道長臼闻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任囤采,我火速辦了婚禮述呐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蕉毯。我一直安慰自己乓搬,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布代虾。 她就那樣靜靜地躺著进肯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪棉磨。 梳的紋絲不亂的頭發(fā)上江掩,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音乘瓤,去河邊找鬼环形。 笑死,一個胖子當(dāng)著我的面吹牛衙傀,可吹牛的內(nèi)容都是我干的抬吟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼统抬,長吁一口氣:“原來是場噩夢啊……” “哼火本!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蓄喇,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤发侵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后妆偏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡盅弛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年钱骂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挪鹏。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡见秽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出讨盒,到底是詐尸還是另有隱情解取,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布返顺,位于F島的核電站禀苦,受9級特大地震影響蔓肯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜振乏,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一蔗包、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧慧邮,春花似錦调限、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至忆谓,卻和暖如春裆装,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陪毡。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工米母, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人毡琉。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓铁瞒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親桅滋。 傳聞我的和親對象是個殘疾皇子慧耍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容