springboot 整合 shiro ehcache
阿新 • • 發佈:2018-11-23
一、我的專案是多模組,這裡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";
}
到這裡就可以用了(在配置過程中遇到許多問題)