项目场景:
想要实现菜单的动态展示,不同角色权限看到的菜单不同
之前一直想实现root角色下,可以看到所有的level,user角色下只能看到level1。
sec:authorize=“hasAuthority(‘root’)”
sec:authorize=“hasRole(‘root’)”
问题描述
动态菜单不生效
在实现过程中发现,页面总是什么都不显示。(此时的可能只有一种,就是html中的sec:authorize="hasRole('root')"
未生效),但是在页面上已经能看到,获取到的权限是库里的root
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>首页</title>
<!--semantic-ui-->
<link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
<link th:href="@{/css/qinstyle.css}" rel="stylesheet">
</head>
<body>
<!--主容器-->
<div class="ui container">
<div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
<div class="ui secondary menu">
<a class="item" th:href="@{/index}">首页</a>
<!--登录注销-->
<div class="right menu">
<!--!isAuthenticated()如果未登录-->
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/toLoginPage}">
<i class="address card icon"></i> 登录
</a>
</div>
<!--如果已登录:用户名,注销-->
<div sec:authorize="isAuthenticated()">
<a class="item">
用户名:<span sec:authentication="name"> </span>
<!-- principal.authorities获得用户的权限-->
角色:<span sec:authentication="principal.authorities"> </span>
</a>
</div>
<div sec:authorize="isAuthenticated()">
<!-- 如果想改成post,需要改成表单form才行-->
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
<!--已登录
<a th:href="@{/usr/toUserCenter}">
<i class="address card icon"></i> admin
</a>
-->
</div>
</div>
</div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study by 秦疆</h3>
</div>
<div>
<br>
<div class="ui three column stackable grid">
<!--菜单根据用户的角色动态的实现-->
<div class="column" sec:authorize="hasRole('root')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('root')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2</h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('root')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3</h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script th:src="@{/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/js/semantic.min.js}"></script>
</body>
</html>
原因分析:
就是因为不了解hasRole和hasAuthority的底层源码逻辑。代码中设置的hasRole(‘root’),check时候是ROLE_root,而数据库中存的是root,因此无法生效
hasRole及hasAuthority的使用区别
身份不加ROLE_前缀
身份ADMIN能通过的权限 @PreAuthorize(“hasRole(‘ADMIN’)”) //不允许
@PreAuthorize(“hasAuthority(‘ADMIN’)”) //允许
@PreAuthorize(“hasRole(‘ROLE_ADMIN’)”) //不允许
@PreAuthorize(“hasAuthority(‘ROLE_ADMIN’)”) //不允许
身份加ROLE_前缀的好处,可以通过hasRole授权
身份ROLE_ADMIN能通过的权限 @PreAuthorize(“hasRole(‘ROLE_ADMIN’)”) //允许
@PreAuthorize(“hasAuthority(‘ROLE_ADMIN’)”) //允许
@PreAuthorize(“hasRole(‘ADMIN’)”) //允许
@PreAuthorize(“hasAuthority(‘ADMIN’)”) //不允许
总结:hasAuthority能通过的身份必须字符串完全一模一样,而hasRole能通过的身份必须前缀带有ROLE_,同时可以通过两种字符串,一是带有前缀ROLE_,二是不带前缀ROLE_。
二者的源码分析:
public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority) {
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority)));
}
private String rolePrefix = "ROLE_";
public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
return access(withRoleHierarchy(AuthorityAuthorizationManager
.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, new String[] { role })));
}
public static <T> AuthorityAuthorizationManager<T> hasAnyRole(String rolePrefix, String[] roles) {
Assert.notNull(rolePrefix, "rolePrefix cannot be null");
Assert.notEmpty(roles, "roles cannot be empty");
Assert.noNullElements(roles, "roles cannot contain null values");
return hasAnyAuthority(toNamedRolesArray(rolePrefix, roles));
}
private static String[] toNamedRolesArray(String rolePrefix, String[] roles) {
String[] result = new String[roles.length];
for (int i = 0; i < roles.length; i++) {
String role = roles[i];
Assert.isTrue(rolePrefix.isEmpty() || !role.startsWith(rolePrefix), () -> role + " should not start with "
+ rolePrefix + " since " + rolePrefix
+ " is automatically prepended when using hasAnyRole. Consider using hasAnyAuthority instead.");
result[i] = rolePrefix + role;
}
return result;
}
/**
* Creates an instance of {@link AuthorityAuthorizationManager} with the provided
* authorities.
* @param authorities the authorities to check for
* @param <T> the type of object being authorized
* @return the new instance
*/
public static <T> AuthorityAuthorizationManager<T> hasAnyAuthority(String... authorities) {
Assert.notEmpty(authorities, "authorities cannot be empty");
Assert.noNullElements(authorities, "authorities cannot contain null values");
return new AuthorityAuthorizationManager<>(authorities);
}
能看到,当使用hasRole()的时候,会给传入的字符串加上ROLE_前缀。这就要求,不管在代码中有没有设置带前缀的,数据库中都需要带ROLE_前缀
解决方案:
将代码中设置成hasAuthority
springsecurityConfig中
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
//首页所有人都可以访问,功能页只有有权限的人才能访问
httpSecurity
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/","/index").permitAll()
.requestMatchers("/toLogin","/toLoginPage").permitAll()
//hasAuthority判断的时候,数据库里和设置的需要一模一样的
.requestMatchers("/level1/**","/level2/**","/level3/**").hasAuthority("root")
.requestMatchers("/level2/**").hasAuthority("user")
//对静态资源的过滤
.requestMatchers("/css/**","/js/**","/views/**").permitAll()
.anyRequest().authenticated()
//没有权限默认回到登陆页面,也可以不传参数,默认是login.html页面
).formLogin(form -> form.loginPage("/toLoginPage")
//处理前端跳转页面,要跟form表单里的action相同
//不配置会走自己设置的controller
.loginProcessingUrl("/toLogin")
//配置了不管是什么url,认证完成都会走/success
// .successForwardUrl("/success")
.passwordParameter("pwd")
.usernameParameter("username"))
.csrf(csrf -> csrf.disable());
return httpSecurity.build();
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>首页</title>
<!--semantic-ui-->
<link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
<link th:href="@{/css/qinstyle.css}" rel="stylesheet">
</head>
<body>
<!--主容器-->
<div class="ui container">
<div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
<div class="ui secondary menu">
<a class="item" th:href="@{/index}">首页</a>
<!--登录注销-->
<div class="right menu">
<!--!isAuthenticated()如果未登录-->
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/toLoginPage}">
<i class="address card icon"></i> 登录
</a>
</div>
<!--如果已登录:用户名,注销-->
<div sec:authorize="isAuthenticated()">
<a class="item">
用户名:<span sec:authentication="name"> </span>
<!-- principal.authorities获得用户的权限-->
角色:<span sec:authentication="principal.authorities"> </span>
</a>
</div>
<div sec:authorize="isAuthenticated()">
<!-- 如果想改成post,需要改成表单form才行-->
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
<!--已登录
<a th:href="@{/usr/toUserCenter}">
<i class="address card icon"></i> admin
</a>
-->
</div>
</div>
</div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study by 秦疆</h3>
</div>
<div>
<br>
<div class="ui three column stackable grid">
<!--菜单根据用户的角色动态的实现-->
<div class="column" sec:authorize="hasAnyAuthority('root','user')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasAuthority('root')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2</h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasAuthority('root')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3</h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script th:src="@{/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/js/semantic.min.js}"></script>
</body>
</html>