本文是接着上篇博客寫的:Spring boot 入門(三):SpringBoot 集成結合 AdminLTE(Freemarker),利用 generate 自動生成代碼,利用 DataTable 和 PageHelper 進行分頁顯示。按照前面的博客,已經能夠搭建一個簡單的 Spring Boot 系統,本篇博客繼續對此係統進行改造,主要集成了 Shiro 權限認證框架,關於 Shiro 部分,在本人以前的博客(認證與Shiro安全框架)有介紹到,這裏就不作累贅的介紹。html
此係列的博客爲實踐部分,以代碼和搭建系統的過程爲主,如遇到專業名詞,自行查找其含義。前端
系統搭建到目前爲止,主要用到了3個配置類,均與 Shiro 有關,後期隨着項目的擴大,配置文件也會隨之增多。java
,若是不設置此變量,FreeMarker 頁面將不能識別 Shiro 的標籤
,其主要代碼以下:configuration.setSharedVariable("shiro", new ShiroTags());
複製代碼
在每次請求裏面都作了 session 的讀取和更新訪問時間等操做,這樣在集羣部署 session 共享的狀況下,數量級的加大了處理量負載
。本項目後期將用到分佈式,所以這裏就直接將過濾器與 Config 配置文件分離,提升效率。private final class MSpringShiroFilter extends AbstractShiroFilter {
protected MSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
super();
if (webSecurityManager == null) {
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
}
setSecurityManager(webSecurityManager);
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
@Override
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String str = request.getRequestURI().toLowerCase();
boolean flag = true;
int idx = 0;
if ((idx = str.indexOf(".")) > 0) {
str = str.substring(idx);
if (ignoreExt.contains(str.toLowerCase()))
flag = false;
}
if (flag) {
super.doFilterInternal(servletRequest, servletResponse, chain);
} else {
chain.doFilter(servletRequest, servletResponse);
}
}
}
複製代碼
/**
* 設置shiro的緩存,緩存參數均配置在xml文件中
* @return
*/
@Bean
public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:ehcache/ehcache-shiro.xml");
return em;
}
/**
* 憑證匹配器
* (因爲咱們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了
* 因此咱們須要修改下doGetAuthenticationInfo中的代碼;
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:這裏使用MD5算法;
hashedCredentialsMatcher.setHashIterations(1);//散列的次數,好比散列兩次,至關於 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
*
* 主文件
*/
@Bean(name = "myShiroRealm")
public UserRealm myShiroRealm(EhCacheManager cacheManager) {
UserRealm realm = new UserRealm();
realm.setCacheManager(cacheManager);
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
//會話ID生成器
@Bean(name = "sessionIdGenerator")
public JavaUuidSessionIdGenerator javaUuidSessionIdGenerator(){
JavaUuidSessionIdGenerator javaUuidSessionIdGenerator = new JavaUuidSessionIdGenerator();
return javaUuidSessionIdGenerator;
}
@Bean(name = "sessionIdCookie")
public SimpleCookie getSessionIdCookie(){
SimpleCookie sessionIdCookie = new SimpleCookie("sid");
sessionIdCookie.setHttpOnly(true);
sessionIdCookie.setMaxAge(-1);
return sessionIdCookie;
}
/*<!-- 會話DAO -->*/
@Bean(name = "sessionDAO")
public EnterpriseCacheSessionDAO enterpriseCacheSessionDAO(){
EnterpriseCacheSessionDAO sessionDao = new EnterpriseCacheSessionDAO();
sessionDao.setSessionIdGenerator(javaUuidSessionIdGenerator());
sessionDao.setActiveSessionsCacheName("shiro-activeSessionCache");
return sessionDao;
}
@Bean(name = "sessionValidationScheduler")
public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler() {
ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
scheduler.setInterval(1800000);
return scheduler;
}
@Bean(name = "sessionManager")
public DefaultWebSessionManager sessionManager(EnterpriseCacheSessionDAO sessionDAO){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(1800000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler());
sessionManager.setSessionDAO(sessionDAO);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(getSessionIdCookie());
return sessionManager;
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm myShiroRealm, DefaultWebSessionManager sessionManager) {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setRealm(myShiroRealm);
// <!-- 用戶受權/認證信息Cache, 採用EhCache 緩存 -->
dwsm.setCacheManager(getEhCacheManager());
dwsm.setSessionManager(sessionManager);
return dwsm;
}
/**
* 開啓shiro aop註解支持.
* 使用代理方式;因此須要開啓代碼支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager);
return aasa;
}
/**
* ShiroFilter<br/>
* 注意這裏參數中的 StudentService 和 IScoreDao 只是一個例子,由於咱們在這裏能夠用這樣的方式獲取到相關訪問數據庫的對象,
* 而後讀取數據庫相關配置,配置到 shiroFilterFactoryBean 的訪問規則中。實際項目中,請使用本身的Service來處理業務邏輯。
*
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new MShiroFilterFactoryBean();
// 必須設置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 若是不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登陸成功後要跳轉的鏈接
shiroFilterFactoryBean.setSuccessUrl("/certification");
//shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
loadShiroFilterChain(shiroFilterFactoryBean);
return shiroFilterFactoryBean;
}
/**
* 加載shiroFilter權限控制規則(從數據庫讀取而後配置)
*
*/
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){
/////////////////////// 下面這些規則配置最好配置到配置文件中 ///////////////////////
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// authc:該過濾器下的頁面必須驗證後才能訪問,它是Shiro內置的一個攔截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
filterChainDefinitionMap.put("/login", "authc");
filterChainDefinitionMap.put("/logout", "logout");
// anon:它對應的過濾器裏面是空的,什麼都沒作
logger.info("##################從數據庫讀取權限規則,加載到shiroFilter中##################");
// filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");// 這裏爲了測試,固定寫死的值,也能夠從數據庫或其餘配置中讀取
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}
複製代碼
主要重寫了 Realm域,完成權限認證和權限管理:web
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//若是沒有作權限驗證,此處只須要return null便可
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String userName = (String) principals.getPrimaryPrincipal();
Result<TUser> list = userService.getUserByUsername(userName);
if(list.isStatus()) {
//獲取該用戶所屬的角色
Result<List<TRole>> resultRole = roleService.getRoleByUserId(list.getResultData().getUserId());
if(resultRole.isStatus()) {
HashSet<String> role = new HashSet<String>();
for(TRole tRole : resultRole.getResultData()) {
role.add(tRole.getRoleId()+"");
}
//獲取該角色擁有的權限
Result<List<TPermission>> resultPermission = permissionService.getPermissionsByRoleId(role);
if(resultPermission.isStatus()) {
HashSet<String> permissions = new HashSet<String>();
for(TPermission tPermission : resultPermission.getResultData()) {
permissions.add(tPermission.getPermissionsValue());
}
System.out.println("權限:"+permissions);
authorizationInfo.setStringPermissions(permissions);
}
}
}
//return null;
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//認證登陸
String username = (String) authenticationToken.getPrincipal();
//String password = new String((char[]) authenticationToken.getCredentials());
Result<TUser> result = userService.getUserByUsername(username);
if (result.isStatus()) {
TUser user = result.getResultData();
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
}
//return new SimpleAuthenticationInfo(user., "123456", getName());
return null;
}
}
複製代碼
首先建立一個前端登陸界面,作一個簡單的登陸 Form 表單算法
必須是Post請求,不然Shiro不能識別
,認證部分主要在 Ream 中完成,新建一個類,繼承 AuthorizingRealm ,而後在重寫 doGetAuthenticationInfo 方法:
new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName())
),咱們能夠本身從新定義密碼比較器,密碼比較器的寫法較多,在
認證與Shiro安全框架中,直接將密碼比較器寫入到Ream中,耦合度過高,本項目經過配置的方式重寫密碼比較器,具體代碼請參考參考ShiroConfiguration配置類:
在具體的 Login 方法中,寫入一些登陸失敗的異常便可,主要用戶將此失敗結果存入 Session,並顯示在頁面上:數據庫
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String postLogin(RedirectAttributes redirectAttributes, HttpServletRequest request, HttpSession session) {
// 登陸失敗從request中獲取shiro處理的異常信息。
// shiroLoginFailure:就是shiro異常類的全類名.
String exception = (String) request.getAttribute("shiroLoginFailure");
System.out.println("exception=" + exception);
String msg = "";
if (exception != null) {
if (UnknownAccountException.class.getName().equals(exception)) {
System.out.println("UnknownAccountException -- > 帳號不存在:");
msg = "用戶不存在!";
} else if (IncorrectCredentialsException.class.getName().equals(exception)) {
System.out.println("IncorrectCredentialsException -- > 密碼不正確:");
msg = "密碼不正確!";
} else if ("kaptchaValidateFailed".equals(exception)) {
System.out.println("kaptchaValidateFailed -- > 驗證碼錯誤");
msg = "驗證碼錯誤!";
} else {
//msg = "else >> "+exception;
msg = "密碼不正確!";
System.out.println("else -- >" + exception);
}
}
redirectAttributes.addFlashAttribute("msg", msg);
session.setAttribute("msg", msg);
//return redirect("/login");
return "redirect:login";
//return msg;
}
複製代碼
此時登陸認證部門已經完成:一個頁面+後臺2個函數(1個認證函數+1個Login函數)apache
整體來講,權限管理只須要在界面增長 Shiro 的權限標籤便可,可使用角色的標籤,也可使用權限的標籤,通常狀況下2種標籤配合使用,效果最好 <@shiro.hasPermission name="xtgl-yhgl:read">
<@shiro.hasRolen name="xtgl-yhgl:read">
緩存
authorizationInfo.setStringPermissions(permissions);
authorizationInfo.setRoles(role);
本項目也是經過此邏輯完成權限管理的安全
到此,Spring Boot集成Shiro框架的權限認證已經搭建完畢,能夠實現簡單的權限管理。bash
較上一篇博客,Shiro 部分新增長的文件