1. 程式人生 > >springboot 整合 shiro ehcache

springboot 整合 shiro ehcache

一、我的專案是多模組,這裡shiro單獨一個模組,下面的三個有用到的類

 

二、新增依賴包

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>

 

三、新建ShiroConfiguration.java(基本網上copy,自己根據需要修改了點東西)

@Configuration
public class ShiroConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);
    @Bean
    public EhCacheManager getEhCacheManager() {
        EhCacheManager em = new EhCacheManager();
        em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
        return em;
    }
    @Bean(name = "myShiroRealm")
    public MyShiroRealm myShiroRealm(EhCacheManager cacheManager) {
        MyShiroRealm realm = new MyShiroRealm();
        realm.setCacheManager(cacheManager);
        return realm;
    }
    /**
     * 註冊DelegatingFilterProxy(Shiro)
     * 整合Shiro有2種方法:
     * 1. 按這個方法自己組裝一個FilterRegistrationBean(這種方法更為靈活,可以自己定義UrlPattern,
     * 在專案使用中你可能會因為一些很但疼的問題最後採用它, 想使用它你可能需要看官網或者已經很瞭解Shiro的處理原理了)
     * 2. 直接使用ShiroFilterFactoryBean(這種方法比較簡單,其內部對ShiroFilter做了組裝工作,無法自己定義UrlPattern,
     * 預設攔截 /*)
     */
//  @Bean
//  public FilterRegistrationBean filterRegistrationBean() {
//      FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
//      filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
//      //  該值預設為false,表示生命週期由SpringApplicationContext管理,設定為true則表示由ServletContainer管理
//      filterRegistration.addInitParameter("targetFilterLifecycle", "true");
//      filterRegistration.setEnabled(true);
//      filterRegistration.addUrlPatterns("/*");// 可以自己靈活的定義很多,避免一些根本不需要被Shiro處理的請求被包含進來
//      return filterRegistration;
//  }

    @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(MyShiroRealm myShiroRealm) {
        DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
        dwsm.setRealm(myShiroRealm);
//      <!-- 使用者授權/認證資訊Cache, 採用EhCache 快取 -->
        dwsm.setCacheManager(getEhCacheManager());
        return dwsm;
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
        aasa.setSecurityManager(securityManager);
        return aasa;
    }
    /**
     * 載入shiroFilter許可權控制規則(從資料庫讀取然後配置)
     */
    private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){/*, UserService userService*/
        /////////////////////// 下面這些規則配置最好配置到配置檔案中 ///////////////////////
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //自定義攔截器 驗證碼使用
        //Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        //filters.put("captchaVaildate", new CaptchaValidateFilter());
        //filters.put("authc111", new MyFormAuthenticationFilter());
        // authc:該過濾器下的頁面必須驗證後才能訪問,它是Shiro內建的一個攔截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
        // anon:它對應的過濾器裡面是空的,什麼都沒做
        logger.info("##################從資料庫讀取許可權規則,載入到shiroFilter中##################");
        //使用自定義攔截器
        filterChainDefinitionMap.put("/sys/logining", "captchaVaildate");
        //filterChainDefinitionMap.put("/sys/main", "authc,roles[admin]");//這裡跳轉方法時候會需要許可權校驗
        // filterChainDefinitionMap.put("/sysUser/queryUser", "authc,roles[admin]");//這裡跳轉方法時候會需要許可權校驗(是否有角色),無許可權時候跳轉/sys/403
        filterChainDefinitionMap.put("/sys/main", "authc");
        filterChainDefinitionMap.put("/sys/403", "anon");
        filterChainDefinitionMap.put("/sys/logout", "anon");
        filterChainDefinitionMap.put("/sysUser/**", "authc");
        filterChainDefinitionMap.put("/**", "anon");//anon 可以理解為不攔截
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必須設定 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/sys/login");//未登入時候跳轉
        // 登入成功後要跳轉的連線
        shiroFilterFactoryBean.setSuccessUrl("/sys/main");
        shiroFilterFactoryBean.setUnauthorizedUrl("/sys/403");//無許可權時候跳轉
        loadShiroFilterChain(shiroFilterFactoryBean);/*, userService*/
        return shiroFilterFactoryBean;
    }

}

四、新建 MyShiroRealm.java

public class MyShiroRealm extends AuthorizingRealm{
    private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
    @Autowired
    private ISysUserService iSysUserService;
    @Autowired
    private SysUserMapper sysUserMapper;
    /**
     * 許可權認證,為當前登入的Subject授予角色和許可權
     * 本例中該方法的呼叫時機為需授權資源被訪問時
     * 並且每次訪問需授權資源時都會執行該方法中的邏輯,這表明本例中預設並未啟用AuthorizationCache
     * 如果連續訪問同一個URL(比如重新整理),該方法不會被重複呼叫,Shiro有一個時間間隔(也就是cache時間,在ehcache-shiro.xml中配置),超過這個時間間隔再重新整理頁面,該方法會被執行
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("##################執行Shiro許可權認證1111##################");
        //獲取當前登入輸入的使用者名稱,等價於(String) principalCollection.fromRealm(getName()).iterator().next();
        String loginName = (String)super.getAvailablePrincipal(principalCollection);
        //到資料庫查是否有此物件
        QueryWrapper<SysUser> wrapper = new QueryWrapper<SysUser>();
        wrapper.eq("user_name",loginName);
        SysUser sysUser=sysUserMapper.selectOne(wrapper);// 實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法
        if(sysUser!=null){
            //許可權資訊物件info,用來存放查出的使用者的所有的角色(role)及許可權(permission)
            SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
            //使用者的角色集合
           // info.setRoles(user.getSysRolesName());
            Set<String> roles=new HashSet<String>();
            List<Map<String,Object>> mapList=iSysUserService.selectUserRoleById(sysUser.getId());
            for (Map<String, Object> map : mapList) {
                for (Map.Entry<String, Object> m : map.entrySet()) {
                    roles.add(m.getValue().toString());
                }
            }
            info.addRoles(roles); //角色 
            return info;
        }
        // 返回null的話,就會導致任何使用者訪問被攔截的請求時,都會自動跳轉到unauthorizedUrl指定的地址
        return null;
    }

    /**
     * 登入認證
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
        //UsernamePasswordToken物件用來存放提交的登入資訊
        UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
        logger.info("驗證當前Subject時獲取到token為:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
        //查出是否有此使用者
      //  User user=userDao.findByName(token.getUsername());
        QueryWrapper<SysUser> wrapper = new QueryWrapper<SysUser>();
        wrapper.eq("user_name",token.getUsername());
        SysUser sysUser=sysUserMapper.selectOne(wrapper);
        if(sysUser!=null){
            // 若存在,將此使用者存放到登入認證info中,無需自己做密碼對比,Shiro會為我們進行密碼對比校驗
            return new SimpleAuthenticationInfo(sysUser.getUserName(), sysUser.getPassword(), getName());
        }
        return null;
    }
}

五、新建MShiroFilterFactoryBean.java ,繼承 ShiroFilterFactoryBean 處理攔截資原始檔問題。

public class MShiroFilterFactoryBean extends ShiroFilterFactoryBean {
    // 對ShiroFilter來說,需要直接忽略的請求
    private Set<String> ignoreExt;

    public MShiroFilterFactoryBean() {
        super();
        ignoreExt = new HashSet<String>();
        ignoreExt.add(".jpg");
        ignoreExt.add(".png");
        ignoreExt.add(".gif");
        ignoreExt.add(".bmp");
        ignoreExt.add(".js");
        ignoreExt.add(".css");
    }
    @Override
    protected AbstractShiroFilter createInstance() throws Exception {

        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }

        FilterChainManager manager = createFilterChainManager();

        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        return new MSpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }

    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();
            // 因為ShiroFilter 攔截所有請求(在上面我們配置了urlPattern 為 * ,當然你也可以在那裡精確的新增要處理的路徑,這樣就不需要這個類了),而在每次請求裡面都做了session的讀取和更新訪問時間等操作,這樣在叢集部署session共享的情況下,數量級的加大了處理量負載。
            // 所以我們這裡將一些能忽略的請求忽略掉。
            // 當然如果你的集群系統使用了動靜分離處理,靜態資料的請求不會到Filter這個層面,便可以忽略。
            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);
            }
        }
    }
}

六、新建ehcache-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />
</ehcache>

七、寫登入退出方法

@RequestMapping(value="logining")
@ResponseBody
public JSONObject login(HttpServletRequest request, Model model, HttpServletResponse response, @Valid SysUser user, BindingResult bindingResult, RedirectAttributes redirectAttributes){
    JSONObject json=new JSONObject();
    model.addAttribute("kry","ssss");
    if(bindingResult.hasErrors()){
        json.put("flag","faliure");
        return json;
    }
    String userName = user.getUserName();
    //此處表單提交一個是否rememberMe單選框,根據值request.getParameter("rememberMe")來判斷是否新增“記住”功能
    //UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPwd(),true);
    UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(),Md5Utils.md5(user.getPassword()));
    //獲取當前的Subject
    Subject currentUser = SecurityUtils.getSubject();
    try {
        //在呼叫了login方法後,SecurityManager會收到AuthenticationToken,並將其傳送給已配置的Realm執行必須的認證檢查
        //每個Realm都能在必要時對提交的AuthenticationTokens作出反應
        //所以這一步在呼叫login(token)方法時,它會走到MyRealm.doGetAuthenticationInfo()方法中,具體驗證方式詳見此方法
        logger.info("對使用者[" + userName + "]進行登入驗證..驗證開始");
        currentUser.login(token);
        Session session = currentUser.getSession(true);
        //主機IP
        System.out.println("host:"+session.getHost());
        //session超時時間
        session.setTimeout(1800000);//30分鐘
        // session.setTimeout(5000);//
        System.out.println("timeout:"+session.getTimeout());
        logger.info("對使用者[" + userName + "]進行登入驗證..驗證通過");
    }catch(UnknownAccountException uae){
        logger.info("對使用者[" + userName + "]進行登入驗證..驗證未通過,未知賬戶");
        redirectAttributes.addFlashAttribute("message", "未知賬戶");
        json.put("message","未知賬戶");
    }catch(IncorrectCredentialsException ice){
        logger.info("對使用者[" + userName + "]進行登入驗證..驗證未通過,錯誤的憑證");
        redirectAttributes.addFlashAttribute("message", "密碼不正確");
        json.put("message","密碼不正確");
    }catch(LockedAccountException lae){
        logger.info("對使用者[" + userName + "]進行登入驗證..驗證未通過,賬戶已鎖定");
        redirectAttributes.addFlashAttribute("message", "賬戶已鎖定");
        json.put("message","賬戶已鎖定");
    }catch(ExcessiveAttemptsException eae){
        logger.info("對使用者[" + userName + "]進行登入驗證..驗證未通過,錯誤次數過多");
        redirectAttributes.addFlashAttribute("message", "使用者名稱或密碼錯誤次數過多");
        json.put("message","使用者名稱或密碼錯誤次數過多");
    }catch(AuthenticationException ae){
        //通過處理Shiro的執行時AuthenticationException就可以控制使用者登入失敗或密碼錯誤時的情景
        logger.info("對使用者[" + userName + "]進行登入驗證..驗證未通過,堆疊軌跡如下");
        ae.printStackTrace();
        redirectAttributes.addFlashAttribute("message", "使用者名稱或密碼不正確");
        json.put("message","使用者名稱或密碼不正確");
    }
   
    //驗證是否登入成功
    if(currentUser.isAuthenticated()){
        logger.info("使用者[" + userName + "]登入認證通過(這裡可以進行一些認證通過後的一些系統引數初始化操作)");
        if (currentUser.hasRole("admin")) {
            logger.info("-----------admin---------");
        }
        if (currentUser.hasRole("user")) {
            logger.info("-----------user---------");
        }
        json.put("flag","success");
        return json;
    }else{
        token.clear();
        json.put("flag","failure");
        return json;
    }
}
@RequestMapping(value="/logout",method=RequestMethod.GET)
public String logout(RedirectAttributes redirectAttributes ){
    //使用許可權管理工具進行使用者的退出,跳出登入,給出提示資訊
    SecurityUtils.getSubject().logout();
    redirectAttributes.addFlashAttribute("message", "您已安全退出");
    logger.info("------您已安全退出-------");
    return "redirect:/sys/login";
}

@RequestMapping("/403")
public String unauthorizedRole(){
    logger.info("------沒有許可權-------");
    return "sys/403";
}

到這裡就可以用了(在配置過程中遇到許多問題)