本篇內(nèi)容主要講解“spring security中的權(quán)限控制是什么意思”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“spring security中的權(quán)限控制是什么意思”吧!
創(chuàng)新互聯(lián)公司主要從事網(wǎng)站制作、做網(wǎng)站、網(wǎng)頁(yè)設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)潁東,十多年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來(lái)電咨詢建站服務(wù):13518219792
當(dāng)我們?cè)贠Auth登陸后,獲取了登陸的令牌,使用該令牌,我們就有了訪問(wèn)一些受OAuth保護(hù)的接口的能力。具體可以看本人的這兩篇博客OAuth3.0用戶名,密碼登錄解析 OAuth3.0通過(guò)token獲取受保護(hù)資源的解析
但現(xiàn)在我們要區(qū)分這些登陸人員的具體分工,哪些接口歸哪些登陸人員可以訪問(wèn),這就要用到了spring security中的權(quán)限控制。
首先我們需要有一個(gè)權(quán)限的對(duì)象
/** * 權(quán)限標(biāo)識(shí) */@Datapublic class SysPermission implements Serializable { private static final long serialVersionUID = 280565233032255804L; private Long id; //權(quán)限id private String permission; //具體的權(quán)限 private String name; //權(quán)限名稱 private Date createTime; private Date updateTime;}
對(duì)應(yīng)于數(shù)據(jù)庫(kù)中的權(quán)限表
那么問(wèn)題來(lái)了,我們要對(duì)權(quán)限進(jìn)行管理需要什么樣的權(quán)限呢,當(dāng)然我們需要權(quán)限管理權(quán)限,這是在系統(tǒng)一開始建立的時(shí)候保存進(jìn)數(shù)據(jù)庫(kù)的
這四個(gè)權(quán)限并不是通過(guò)前端寫入的。
現(xiàn)在我們需要通過(guò)前端接口增加其他的權(quán)限就需要使用到這四個(gè)權(quán)限之一。
在這里我們給出一些權(quán)限的增刪改查的mybatis dao
@Mapperpublic interface SysPermissionDao { @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("insert into sys_permission(permission, name, createTime, updateTime) values(#{permission}, #{name}, #{createTime}, #{createTime})") int save(SysPermission sysPermission); @Update("update sys_permission t set t.name = #{name}, t.permission = #{permission}, t.updateTime = #{updateTime} where t.id = #{id}") int update(SysPermission sysPermission); @Delete("delete from sys_permission where id = #{id}") int delete(Long id); @Select("select * from sys_permission t where t.id = #{id}") SysPermission findById(Long id); @Select("select * from sys_permission t where t.permission = #{permission}") SysPermission findByPermission(String permission); int count(Mapparams); List findData(Map params);}
現(xiàn)在我們要在Controller中增加一個(gè)新的權(quán)限
/** * 管理后臺(tái)添加權(quán)限 * * @param sysPermission * @return */@LogAnnotation(module = LogModule.ADD_PERMISSION)@PreAuthorize("hasAuthority('back:permission:save')")@PostMapping("/permissions")public SysPermission save(@RequestBody SysPermission sysPermission) { if (StringUtils.isBlank(sysPermission.getPermission())) { throw new IllegalArgumentException("權(quán)限標(biāo)識(shí)不能為空"); } if (StringUtils.isBlank(sysPermission.getName())) { throw new IllegalArgumentException("權(quán)限名不能為空"); } sysPermissionService.save(sysPermission); return sysPermission;}
我們可以看到這個(gè)標(biāo)簽@PreAuthorize("hasAuthority('back:permission:save')"),首先我們是通過(guò)access_token令牌訪問(wèn)的該接口,系統(tǒng)可以知道登陸的是哪一個(gè)用戶,以此看看該用戶是否有back:permission:save的訪問(wèn)權(quán)限
我們來(lái)看看用戶角色
@Datapublic class SysRole implements Serializable { private static final long serialVersionUID = -2054359538140713354L; private Long id; //角色id private String code; //角色編碼 private String name; //角色名稱 private Date createTime; private Date updateTime;}
對(duì)應(yīng)數(shù)據(jù)庫(kù)中的表結(jié)構(gòu)如下
并給定一個(gè)管理員角色
該角色對(duì)應(yīng)于哪些權(quán)限,這里可以看到是所有權(quán)限
而我們的用戶是哪個(gè)角色呢
我們可以看到這里有兩個(gè)用戶,他們都屬于管理員角色
如果我們現(xiàn)在用其中的一個(gè)用戶登陸,并獲取該用戶的信息如下
{
"code" : 200 ,
"data" : {
"access_token" : "aaf4cd90-497e-4c33-adde-b580ab0f0c65" ,
"user" : {
"accountNonExpired" : true ,
"accountNonLocked" : true ,
"authorities" : [
{
"authority" : "back:menu:set2role"
},
{
"authority" : "mail:update"
},
{
"authority" : "back:permission:delete"
},
{
"authority" : "role:permission:byroleid"
},
{
"authority" : "back:menu:save"
},
{
"authority" : "back:menu:query"
},
{
"authority" : "ip:black:query"
},
{
"authority" : "ip:black:save"
},
{
"authority" : "file:del"
},
{
"authority" : "ip:black:delete"
},
{
"authority" : "mail:query"
},
{
"authority" : "back:user:query"
},
{
"authority" : "back:role:permission:set"
},
{
"authority" : "sms:query"
},
{
"authority" : "back:role:query"
},
{
"authority" : "back:permission:query"
},
{
"authority" : "back:user:role:set"
},
{
"authority" : "back:role:save"
},
{
"authority" : "log:query"
},
{
"authority" : "file:query"
},
{
"authority" : "back:menu:update"
},
{
"authority" : "back:role:update"
},
{
"authority" : "back:role:delete"
},
{
"authority" : "back:user:password"
},
{
"authority" : "ROLE_SUPER_ADMIN"
},
{
"authority" : "back:menu:delete"
},
{
"authority" : "back:user:update"
},
{
"authority" : "menu:byroleid"
},
{
"authority" : "mail:save"
},
{
"authority" : "user:role:byuid"
},
{
"authority" : "back:permission:save"
},
{
"authority" : "back:permission:update"
}
],
"createTime" : "2018-01-17T16:56:59.000+0800" ,
"credentialsNonExpired" : true ,
"enabled" : true ,
"headImgUrl" : "" ,
"id" : 1 ,
"nickname" : "測(cè)試1" ,
"password" : "$2a$10$QpeXBJpWYetNwfWEHnkvLeK0jS0P9R6V8QqCj37zeNGroqYvdvW.C" ,
"permissions" : [
"back:menu:set2role" ,
"mail:update" ,
"back:permission:delete" ,
"role:permission:byroleid" ,
"back:menu:save" ,
"back:menu:query" ,
"ip:black:query" ,
"ip:black:save" ,
"file:del" ,
"ip:black:delete" ,
"mail:query" ,
"back:user:query" ,
"back:role:permission:set" ,
"sms:query" ,
"back:role:query" ,
"back:permission:query" ,
"back:user:role:set" ,
"back:role:save" ,
"log:query" ,
"file:query" ,
"back:menu:update" ,
"back:role:update" ,
"back:role:delete" ,
"back:user:password" ,
"back:menu:delete" ,
"back:user:update" ,
"menu:byroleid" ,
"mail:save" ,
"user:role:byuid" ,
"back:permission:save" ,
"back:permission:update"
],
"phone" : "" ,
"sex" : 1 ,
"sysRoles" : [
{
"code" : "SUPER_ADMIN" ,
"createTime" : "2018-01-19T20:32:16.000+0800" ,
"id" : 1 ,
"name" : "超級(jí)管理員" ,
"updateTime" : "2018-01-19T20:32:18.000+0800"
}
],
"type" : "APP" ,
"updateTime" : "2018-01-17T16:57:01.000+0800" ,
"username" : "admin"
}
},
"msg" : "操作成功"
}
通過(guò)返回的信息我們可以看到他的權(quán)限,跟 @PreAuthorize( "hasAuthority('back:permission:save')")比對(duì),我們知道登陸用戶擁有該權(quán)限??梢栽L問(wèn)該接口。
上面都是前菜,下面進(jìn)入正題。
我們來(lái)看一下 @PreAuthorize 標(biāo)簽的源碼,它位于org.springframework.security.access.prepost包下
/** * 用于指定將計(jì)算為的方法訪問(wèn)控制表達(dá)式的批注 * 決定是否允許方法調(diào)用。 * * @author Luke Taylor * @since 3.0 */@Target({ ElementType.METHOD, ElementType.TYPE })@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface PreAuthorize { /** * @return 在執(zhí)行這個(gè)受保護(hù)的方法前進(jìn)行Spring EL表達(dá)式的解析 */ String value();}
這里有一個(gè)Spring EL表達(dá)式都解析,我們來(lái)看一下什么是Spring EL表達(dá)式
public class SpringELTest {public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); //解析字符串,該字符串具有一段代碼的味道 Expression expression = parser.parseExpression("'Hello World'.bytes.length"); int length = (int)expression.getValue(); System.out.println(length); } }
這個(gè)"'Hello World'.bytes.length"就是一段Spring EL表達(dá)式,雖然是一段字符串,但它有一段代碼的含義,可以被解析執(zhí)行
運(yùn)行結(jié)果
11
那么很明顯"hasAuthority('back:permission:save')"就是一段Spring EL表達(dá)式,它是可以被執(zhí)行的。
要想使標(biāo)簽@PreAuthorize生效,我們需要設(shè)置一下OAuth的資源服務(wù)設(shè)置
/** * 資源服務(wù)配置 */@EnableResourceServer@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable().exceptionHandling() .authenticationEntryPoint( (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED)) .and().authorizeRequests().antMatchers(PermitAllUrl.permitAllUrl("/users-anon/**", "/sys/login","/wechat/**")).permitAll() .anyRequest().authenticated().and().httpBasic(); } @Override public void configure(ResourceServerSecurityConfigurer resource) throws Exception { //這里把自定義異常加進(jìn)去 resource.authenticationEntryPoint(new AuthExceptionEntryPoint()) .accessDeniedHandler(new CustomAccessDeniedHandler()); } @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } }
其中@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)就是打開@PreAuthorize進(jìn)行攔截生效的標(biāo)簽,當(dāng)然一定要設(shè)置prePostEnabled = true。
既然"hasAuthority('back:permission:save')"是一段Spring EL表達(dá)式,那么hasAuthority()就一定是一個(gè)可以執(zhí)行的方法,該方法位于SecurityExpressionRoot類下,該類位于org.springframework.security.access.expression包中。
Spring Security可用表達(dá)式對(duì)象的基類就是SecurityExpressionRoot,它支持很多的方法
表達(dá)式 | 描述 |
hasRole([role]) | 當(dāng)前用戶是否擁有指定角色。 |
hasAnyRole([role1,role2]) | 多個(gè)角色是一個(gè)以逗號(hào)進(jìn)行分隔的字符串。如果當(dāng)前用戶擁有指定角色中的任意一個(gè)則返回true。 |
hasAuthority([auth]) | 等同于hasRole |
hasAnyAuthority([auth2,auth3]) | 等同于hasAnyRole |
Principle | 代表當(dāng)前用戶的principle對(duì)象 |
authentication | 直接從SecurityContext獲取的當(dāng)前Authentication對(duì)象 |
permitAll | 總是返回true,表示允許所有的 |
denyAll | 總是返回false,表示拒絕所有的 |
isAnonymous() | 當(dāng)前用戶是否是一個(gè)匿名用戶 |
isRememberMe() | 表示當(dāng)前用戶是否是通過(guò)Remember-Me自動(dòng)登錄的 |
isAuthenticated() | 表示當(dāng)前用戶是否已經(jīng)登錄認(rèn)證成功了。 |
isFullyAuthenticated() | 如果當(dāng)前用戶既不是一個(gè)匿名用戶,同時(shí)又不是通過(guò)Remember-Me自動(dòng)登錄的,則返回true。 |
我們具體看一下hasAuthority這個(gè)方法的實(shí)現(xiàn),只有當(dāng)這個(gè)方法返回的結(jié)果為true的時(shí)候,我們才能進(jìn)一步訪問(wèn)我們的接口代碼
這里面?zhèn)魅氲腶uthority為"back:permission:save"
public final boolean hasAuthority(String authority) { return hasAnyAuthority(authority);}
public final boolean hasAnyAuthority(String... authorities) { return hasAnyAuthorityName(null, authorities);}
private boolean hasAnyAuthorityName(String prefix, String... roles) { //獲取所有的用戶角色權(quán)限 SetroleSet = getAuthoritySet(); //由于我們這里傳入的roles只有"back:permission:save" //所以role即為"back:permission:save",prefix則為null for (String role : roles) { //defaultedRole依然為"back:permission:save" String defaultedRole = getRoleWithDefaultPrefix(prefix, role); //在權(quán)限集合中是否包含"back:permission:save"的該權(quán)限 //根據(jù)我們之前登錄的返回信息,可以看到"authority": "back:permission:save"的存在 //所以此處是可以通過(guò)權(quán)限驗(yàn)證的。 if (roleSet.contains(defaultedRole)) { return true; } } return false;}
private SetgetAuthoritySet() { //Set roles是SecurityExpressionRoot的屬性 //我們可以看到它是從一系列用戶認(rèn)證里面獲取到的權(quán)限集合 if (roles == null) { roles = new HashSet<>(); //authentication是SecurityExpressionRoot極為重要的一個(gè)屬性,它本身是一個(gè)接口 //管理著用戶認(rèn)證信息的各個(gè)方法 Collection extends GrantedAuthority> userAuthorities = authentication .getAuthorities(); if (roleHierarchy != null) { userAuthorities = roleHierarchy .getReachableGrantedAuthorities(userAuthorities); } roles = AuthorityUtils.authorityListToSet(userAuthorities); } return roles;}
private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) { if (role == null) { return role; } //由于defaultRolePrefix為null,所以此處返回的就是"back:permission:save" if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) { return role; } if (role.startsWith(defaultRolePrefix)) { return role; } return defaultRolePrefix + role;}
我們可以看一下AuthorityUtils.authorityListToSet()方法
public static SetauthorityListToSet( Collection extends GrantedAuthority> userAuthorities) { Set set = new HashSet<>(userAuthorities.size()); //很明顯這里是把認(rèn)證用戶的所有權(quán)限給轉(zhuǎn)化為Set集合 for (GrantedAuthority authority : userAuthorities) { set.add(authority.getAuthority()); } return set;}
到此,相信大家對(duì)“spring security中的權(quán)限控制是什么意思”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!