日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
SpringSecurity動(dòng)態(tài)權(quán)限實(shí)現(xiàn)方案!

1. 動(dòng)態(tài)管理權(quán)限規(guī)則

通過代碼來配置 URL 攔截規(guī)則和請(qǐng)求 URL 所需要的權(quán)限,這樣就比較死板,如果想要調(diào)整訪問某一個(gè) URL 所需要的權(quán)限,就需要修改代碼。

創(chuàng)新互聯(lián)成立于2013年,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站制作、成都網(wǎng)站設(shè)計(jì)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元召陵做網(wǎng)站,已為上家服務(wù),為召陵各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18980820575

動(dòng)態(tài)管理權(quán)限規(guī)則就是我們將 URL 攔截規(guī)則和訪問 URL 所需要的權(quán)限都保存在數(shù)據(jù)庫中,這樣,在不改變?cè)创a的情況下,只需要修改數(shù)據(jù)庫中的數(shù)據(jù),就可以對(duì)權(quán)限進(jìn)行調(diào)整。

1.1 數(shù)據(jù)庫設(shè)計(jì)

簡單起見,我們這里就不引入權(quán)限表了,直接使用角色表,用戶和角色關(guān)聯(lián),角色和資源關(guān)聯(lián),設(shè)計(jì)出來的表結(jié)構(gòu)如圖 13-9 所示。

圖13-9  一個(gè)簡單的權(quán)限數(shù)據(jù)庫結(jié)構(gòu)

menu 表是相當(dāng)于我們的資源表,它里邊保存了訪問規(guī)則,如圖 13-10 所示。

圖13-10  訪問規(guī)則

role 是角色表,里邊定義了系統(tǒng)中的角色,如圖 13-11 所示。

圖13-11  用戶角色表

user 是用戶表,如圖 13-12 所示。

圖13-12  用戶表

user_role 是用戶角色關(guān)聯(lián)表,用戶具有哪些角色,可以通過該表體現(xiàn)出來,如圖 13-13 所示。

圖13-13  用戶角色關(guān)聯(lián)表

menu_role 是資源角色關(guān)聯(lián)表,訪問某一個(gè)資源,需要哪些角色,可以通過該表體現(xiàn)出來,如圖 13-14 所示。

圖13-14  資源角色關(guān)聯(lián)表

至此,一個(gè)簡易的權(quán)限數(shù)據(jù)庫就設(shè)計(jì)好了(在本書提供的案例中,有SQL腳本)。

1.2 實(shí)戰(zhàn)

項(xiàng)目創(chuàng)建

創(chuàng)建 Spring Boot 項(xiàng)目,由于涉及數(shù)據(jù)庫操作,這里選用目前大家使用較多的 MyBatis 框架,所以除了引入 Web、Spring Security 依賴之外,還需要引入 MyBatis 以及 MySQL 依賴。

最終的 pom.xml 文件內(nèi)容如下:



org.springframework.boot
spring-boot-starter-security


org.springframework.boot
spring-boot-starter-web


org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.3


mysql
mysql-connector-java
runtime

項(xiàng)目創(chuàng)建完成后,接下來在 application.properties 中配置數(shù)據(jù)庫連接信息:

spring.datasource.username=root
spring.datasource.password=123
spring.datasource.url=jdbc:mysql:///security13?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

配置完成后,我們的準(zhǔn)備工作就算完成了。

創(chuàng)建實(shí)體類

根據(jù)前面設(shè)計(jì)的數(shù)據(jù)庫,我們需要?jiǎng)?chuàng)建三個(gè)實(shí)體類。

首先來創(chuàng)建角色類 Role:

public class Role {
private Integer id;
private String name;
private String nameZh;
//省略getter/setter
}

然后創(chuàng)建菜單類 Menu:

public class Menu {
private Integer id;
private String pattern;
private List roles;
//省略getter/setter
}

菜單類中包含一個(gè) roles 屬性,表示訪問該項(xiàng)資源所需要的角色。

最后我們創(chuàng)建 User 類:

public class User implements UserDetails {
private Integer id;
private String password;
private String username;
private boolean enabled;
private boolean locked;
private List roles;
@Override
public Collection getAuthorities() {
return roles.stream()
.map(r -> new SimpleGrantedAuthority(r.getName()))
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
//省略其他getter/setter
}

由于數(shù)據(jù)庫中有 enabled 和 locked 字段,所以 isEnabled() 和 isAccountNonLocked() 兩個(gè)方法如實(shí)返回,其他幾個(gè)賬戶狀態(tài)方法默認(rèn)返回 true 即可。在 getAuthorities() 方法中,我們對(duì) roles 屬性進(jìn)行遍歷,組裝出新的集合對(duì)象返回即可。

創(chuàng)建Service

接下來我們創(chuàng)建 UserService 和 MenuService,并提供相應(yīng)的查詢方法。

先來看 UserService:

@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用戶不存在");
}
user.setRoles(userMapper.getUserRoleByUid(user.getId()));
return user;
}
}

這段代碼應(yīng)該不用多說了,不熟悉的讀者可以參考本書 2.4 節(jié)。

對(duì)應(yīng)的 UserMapper 如下:

@Mapper
public interface UserMapper {
List getUserRoleByUid(Integer uid);
User loadUserByUsername(String username);
}

UserMapper.xml:

        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">



再來看 MenuService,該類只需要提供一個(gè)方法,就是查詢出所有的 Menu 數(shù)據(jù),代碼如下:

@Service
public class MenuService {
@Autowired
MenuMapper menuMapper;
public List getAllMenu() {
return menuMapper.getAllMenu();
}
}

MenuMapper:

@Mapper
public interface MenuMapper {
List getAllMenu();
}

MenuMapper.xml:

        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

type="org.javaboy.base_on_url_dy.model.Menu">


ofType="org.javaboy.base_on_url_dy.model.Role">






需要注意,由于每一個(gè) Menu 對(duì)象都包含了一個(gè) Role 集合,所以這個(gè)查詢是一對(duì)多,這里通過 resultMap 來進(jìn)行查詢結(jié)果映射。

至此,所有基礎(chǔ)工作都完成了,接下來配置 Spring Security。

配置Spring Security

回顧 13.3.6 小節(jié)的內(nèi)容,SecurityMetadataSource 接口負(fù)責(zé)提供受保護(hù)對(duì)象所需要的權(quán)限。在本案例中,受保護(hù)對(duì)象所需要的權(quán)限保存在數(shù)據(jù)庫中,所以我們可以通過自定義類繼承自 FilterInvocationSecurityMetadataSource,并重寫 getAttributes 方法來提供受保護(hù)對(duì)象所需要的權(quán)限,代碼如下:

@Component
public class CustomSecurityMetadataSource
implements FilterInvocationSecurityMetadataSource {
@Autowired
MenuService menuService;
AntPathMatcher antPathMatcher = new AntPathMatcher();

@Override
public Collection getAttributes(Object object)
throws IllegalArgumentException {
String requestURI =
((FilterInvocation) object).getRequest().getRequestURI();
List allMenu = menuService.getAllMenu();
for (Menu menu : allMenu) {
if (antPathMatcher.match(menu.getPattern(), requestURI)) {
String[] roles = menu.getRoles().stream()
.map(r -> r.getName()).toArray(String[]::new);
return SecurityConfig.createList(roles);
}
}
return null;
}

@Override
public Collection getAllConfigAttributes() {
return null;
}

@Override
public boolean supports(Class clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}

自定義 CustomSecurityMetadataSource 類并實(shí)現(xiàn) FilterInvocationSecurityMetadataSource 接口,然后重寫它里邊的三個(gè)方法:

  • getAttributes:該方法的參數(shù)是受保護(hù)對(duì)象,在基于 URL 地址的權(quán)限控制中,受保護(hù)對(duì)象就是 FilterInvocation;該方法的返回值則是訪問受保護(hù)對(duì)象所需要的權(quán)限。在該方法里邊,我們首先從受保護(hù)對(duì)象 FilterInvocation 中提取出當(dāng)前請(qǐng)求的 URL 地址,例如/admin/hello,然后通過 menuService 對(duì)象查詢出所有的菜單數(shù)據(jù)(每條數(shù)據(jù)中都包含訪問該條記錄所需要的權(quán)限),遍歷查詢出來的菜單數(shù)據(jù),如果當(dāng)前請(qǐng)求的 URL 地址和菜單中某一條記錄的 pattern 屬性匹配上了(例如/admin/hello 匹配上/admin/**),那么我們就可以獲取當(dāng)前請(qǐng)求所需要的權(quán)限。從 menu 對(duì)象中獲取 roles 屬性,并將其轉(zhuǎn)為一個(gè)數(shù)組,然后通過SecurityConfig.createList 方法創(chuàng)建一個(gè)Collection 對(duì)象并返回。如果當(dāng)前請(qǐng)求的 URL 地址和數(shù)據(jù)庫中 menu 表的所有項(xiàng)都匹配不上,那么最終返回 null。如果返回 null,那么受保護(hù)對(duì)象到底能不能訪問呢?這就要看 AbstractSecurityInterceptor 對(duì)象中的 rejectPublicInvocations 屬性了,該屬性默認(rèn)為 false,表示當(dāng) getAttributes 方法返回 null 時(shí),允許訪問受保護(hù)對(duì)象(回顧 13.4.4 小節(jié)中關(guān)于AbstractSecurityInterceptor#beforeInvocation 的講解)。
  • getAllConfigAttributes:該方法可以用來返回所有的權(quán)限屬性,以便在項(xiàng)目啟動(dòng)階段做校驗(yàn),如果不需要校驗(yàn),則直接返回 null 即可。
  • supports:該方法表示當(dāng)前對(duì)象支持處理的受保護(hù)對(duì)象是 FilterInvocation。

CustomSecurityMetadataSource 類配置完成后,接下來我們要用它來代替默認(rèn)的 SecurityMetadataSource 對(duì)象,具體配置如下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
CustomSecurityMetadataSource customSecurityMetadataSource;
@Autowired
UserService userService;

@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userService);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
ApplicationContext applicationContext =
http.getSharedObject(ApplicationContext.class);
http.apply(new UrlAuthorizationConfigurer<>(applicationContext))
.withObjectPostProcessor(new
ObjectPostProcessor() {
@Override
public O
postProcess(O object) {
object.setSecurityMetadataSource(customSecurityMetadataSource);
return object;
}
});
http.formLogin()
.and()
.csrf().disable();
}
}

關(guān)于用戶的配置無需多說,我們重點(diǎn)來看 configure(HttpSecurity) 方法。

由于訪問路徑規(guī)則和所需要的權(quán)限之間的映射關(guān)系已經(jīng)保存在數(shù)據(jù)庫中,所以我們就沒有必要在 Java 代碼中配置映射關(guān)系了,同時(shí)這里的權(quán)限對(duì)比也不會(huì)用到權(quán)限表達(dá)式,所以我們通過 UrlAuthorizationConfigurer 來進(jìn)行配置。

在配置的過程中,通過 withObjectPostProcessor 方法調(diào)用 ObjectPostProcessor 對(duì)象后置處理器,在對(duì)象后置處理器中,將 FilterSecurityInterceptor 中的 SecurityMetadataSource 對(duì)象替換為我們自定義的 customSecurityMetadataSource 對(duì)象即可。

2. 測(cè)試

接下來創(chuàng)建 HelloController,代碼如下:

@RestController
public class HelloController {
@GetMapping("/admin/hello")
public String admin() {
return "hello admin";
}
@GetMapping("/user/hello")
public String user() {
return "hello user";
}
@GetMapping("/guest/hello")
public String guest() {
return "hello guest";
}
@GetMapping("/hello")
public String hello() {
return "hello";
}
}

最后啟動(dòng)項(xiàng)目進(jìn)行測(cè)試。

首先使用 admin/123 進(jìn)行登錄,該用戶具備 ROLE_ADMIN 角色,ROLE_ADMIN 可以訪問 /admin/hello、/user/hello 以及 /guest/hello 三個(gè)接口。

接下來使用 user/123 進(jìn)行登錄,該用戶具備 ROLE_USER 角色,ROLE_USER 可以訪問 /user/hello 以及 /guest/hello 兩個(gè)接口。

最后使用 javaboy/123 進(jìn)行登錄,該用戶具備 ROLE_GUEST 角色,ROLE_GUEST 可以訪問 /guest/hello 接口。

由于 /hello 接口不包含在 URL-權(quán)限 映射關(guān)系中,所以任何用戶都可以訪問 /hello 接口,包括匿名用戶。如果希望所有的 URL 地址都必須在數(shù)據(jù)庫中配置 URL-權(quán)限 映射關(guān)系后才能訪問,那么可以通過如下配置實(shí)現(xiàn):

http.apply(new UrlAuthorizationConfigurer<>(applicationContext))
.withObjectPostProcessor(new
ObjectPostProcessor() {
@Override
public O
postProcess(O object) {
object.setSecurityMetadataSource(customSecurityMetadataSource);
object.setRejectPublicInvocations(true);
return object;
}
});

通過設(shè)置 FilterSecurityInterceptor 中的 rejectPublicInvocations 屬性為 true,就可以關(guān)閉URL的公開訪問,所有 URL 必須具備對(duì)應(yīng)的權(quán)限才能訪問。


新聞名稱:SpringSecurity動(dòng)態(tài)權(quán)限實(shí)現(xiàn)方案!
網(wǎng)站路徑:http://m.5511xx.com/article/coidcdh.html