上一篇寫了springboot與redis集群的整合,接下來(lái)我們寫一個(gè)通用業(yè)務(wù)相關(guān)的功能,本來(lái)打算寫elasticsearch相關(guān)的整合的,但是這塊環(huán)境我還不太熟悉,需要了解一下再繼續(xù),以后肯定會(huì)寫的。
我這里打算做成一個(gè)開箱即用的開源系統(tǒng),所以也會(huì)從基礎(chǔ)業(yè)務(wù)開始寫起。
1、權(quán)限認(rèn)證
開始還是先寫一些基礎(chǔ)知識(shí),權(quán)限主要用于資源和用戶的一些校驗(yàn)。
對(duì)于java項(xiàng)目,常用的權(quán)限校驗(yàn)的組件主要有shiro和security,而shiro由于功能簡(jiǎn)單,上手快而很快的被程序員喜愛。這里我們也以shiro為主,參考 shiro官網(wǎng),
我覺得學(xué)習(xí)一門新的東西,最快的就是參考官方文檔。
開濤老師的shiro系列也是非常的贊,跟我學(xué)shiro系列
2、框架的搭建
首先還是在Spring官網(wǎng) 搭建基礎(chǔ)配置
項(xiàng)目中集成了jwt,但是這里還沒有使用,這次只是簡(jiǎn)單的把項(xiàng)目搭建起來(lái),做簡(jiǎn)單的測(cè)試。
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql相關(guān)-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatis相關(guān)-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Spring 整合Shiro需要的依賴 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!-- 日志依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
3、shiroConfig配置
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean 處理攔截資源文件問題。
* 注意:?jiǎn)为?dú)一個(gè)ShiroFilterFactoryBean配置是或報(bào)錯(cuò)的,以為在
* 初始化ShiroFilterFactoryBean的時(shí)候需要注入:SecurityManager
* Filter Chain定義說明 1、一個(gè)URL可以配置多個(gè)Filter,使用逗號(hào)分隔 2、當(dāng)設(shè)置多個(gè)過濾器時(shí),全部驗(yàn)證通過,才視為通過
* 3、部分過濾器可指定參數(shù),如perms,roles
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
factoryBean.getFilters().put("authControlFilter", authControlFilter());
//攔截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
filterChainDefinitionMap.put("/*", "authControlFilter");
filterChainDefinitionMap.put("/favicon.ico", "anon");
factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return factoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSubjectFactory(subjectFactory());
securityManager.setSessionManager(sessionManager());
securityManager.setRealm(authRealm());
/*
* 禁用使用Sessions 作為存儲(chǔ)策略的實(shí)現(xiàn),但它沒有完全地禁用Sessions
* 所以需要配合context.setSessionCreationEnabled(false);
*/
((DefaultSessionStorageEvaluator)((DefaultSubjectDAO)securityManager.getSubjectDAO()).getSessionStorageEvaluator()).setSessionStorageEnabled(false);
return securityManager;
}
/**
* Add.2.1
* subject工廠管理器.
* @return
*/
@Bean
public DefaultWebSubjectFactory subjectFactory(){
StatelessDefaultSubjectFactory subjectFactory = new StatelessDefaultSubjectFactory();
return subjectFactory;
}
/**
* Add.2.4
* session管理器:
* sessionManager通過sessionValidationSchedulerEnabled禁用掉會(huì)話調(diào)度器,
* 因?yàn)槲覀兘玫袅藭?huì)話,所以沒必要再定期過期會(huì)話了。
* @return
*/
@Bean
public DefaultSessionManager sessionManager(){
DefaultSessionManager sessionManager = new DefaultSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(false);
return sessionManager;
}
/**
* 身份認(rèn)證realm; (這個(gè)需要自己寫,賬號(hào)密碼校驗(yàn);權(quán)限等)
*
* @return
*/
@Bean
public AuthRealm authRealm() {
AuthRealm authRealm = new AuthRealm();
return authRealm;
}
/**
* Add.4.1
* 訪問控制器.
* @return
*/
@Bean
public AuthControlFilter authControlFilter(){
AuthControlFilter authControlFilter = new AuthControlFilter();
return authControlFilter;
}
}
備注:這里有很多的坑,網(wǎng)上很多的代碼根本跑步起來(lái)啊,希望小伙伴們寫博客的時(shí)候,把代碼至少自己跑一遍,首先我這里使用的是idea編輯器,eclipse沒有試。
- 1、filterChainDefinitionMap.put("/favicon.ico", "anon"); 這行代碼一定要配置,切記。不然會(huì)執(zhí)行多次
- shiroFilter:主要做資源的攔截,注意所有的請(qǐng)求都會(huì)被 ShiroFilter 攔截并進(jìn)行相應(yīng)的鏈?zhǔn)教幚?,可以說是所有配置的入口,簡(jiǎn)化配置,方便使用。
5、Realm的配置
shiro的認(rèn)證過程最終會(huì)交由Realm執(zhí)行,這時(shí)會(huì)調(diào)用Realm的getAuthenticationInfo(token)方法。
public class AuthRealm extends AuthorizingRealm {
private static final Logger logger = LoggerFactory.getLogger(AuthorizingRealm.class);
private static final String USER_NAME = "admin";
private static final String PASSWORD = "123456";
/**
* 僅支持StatelessToken 類型的Token,
* 那么如果在StatelessAuthcFilter類中返回的是UsernamePasswordToken,那么將會(huì)報(bào)如下錯(cuò)誤信息:
* Please ensure that the appropriate Realm implementation is configured correctly or
* that the realm accepts AuthenticationTokens of this type.StatelessAuthcFilter.isAccessAllowed()
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof StatelessAuthenticationToken;
}
/**
* 認(rèn)證信息.(身份驗(yàn)證) : Authentication 是用來(lái)驗(yàn)證用戶身份
* 授權(quán)
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("StatelessRealm.doGetAuthorizationInfo()");
//根據(jù)用戶名查找角色,請(qǐng)根據(jù)需求實(shí)現(xiàn)
String username = (String) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//這里模擬admin賬號(hào)才有role的權(quán)限.
if("admin".equals(username)){
authorizationInfo.addRole("0");
}
return authorizationInfo;
}
/**
* 登錄驗(yàn)證
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("authRealm.doGetAuthenticationInfo()");
StatelessAuthenticationToken statelessToken = (StatelessAuthenticationToken)authenticationToken;
String username = (String)statelessToken.getPrincipal();//不能為null,否則會(huì)報(bào)錯(cuò)的.
//根據(jù)用戶名獲取密鑰(和客戶端的一樣)
//在服務(wù)器端生成客戶端參數(shù)消息摘要
String serverDigest = DecriptUtil.MD5(USER_NAME+PASSWORD);
logger.info("{$serverDigest}:{}",serverDigest);
logger.info("---------------->"+serverDigest+","+statelessToken.getCredentials());
//然后進(jìn)行客戶端消息摘要和服務(wù)器端消息摘要的匹配
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
statelessToken.getCredentials(),
serverDigest,
getName());
return authenticationInfo;
}
//得到密鑰,此處硬編碼一個(gè).
private String getKey(String username) {
return username;
}
}
接下來(lái)還有DefaultWebSubjectFactory和AuthenticationToken的配置,這里就不多做說明了,具體可以運(yùn)行代碼看看lessons-7
github