一、名詞解釋
網上一大堆
二、pom依賴
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.2</version> </dependency>
encache可選,主要用於鑒權時的緩存
三、shiroConfiguration
shiro的配置主要是shiroFilter和securityManager的設置
@Component public class ShiroConfiguration {
@Bean public EhCacheManager ehCacheManager() { EhCacheManager manager = new EhCacheManager(); manager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return manager; } @Resource private MyShiroRealm myShiroRealm; @Bean public SecurityManager securityManager() { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(myShiroRealm); defaultWebSecurityManager.setCacheManager(ehCacheManager()); return defaultWebSecurityManager; } @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String,String> map = new LinkedHashMap<>(); Map<String,Filter> filterMap = new LinkedHashMap<>(); filterMap.put("authc",loginFilter()); filterMap.put("perms",myFilter()); shiroFilterFactoryBean.setFilters(filterMap); //map.put("/RPCAFA2A208FA648EA27C1EC30CADFC8B3D","anon"); //map.put("/**","authc"); map.put("/RPC52CA3404FDADAB18F91E8210DFCE1522","perms[admin:test]"); map.put("/RPC66EED9EBACF5FB42B9AD9C069495587F","perms[test]"); map.put("/**","authc"); //map.put("/**","myfilter"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /*@Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); //myShiroRealm.setCacheManager(manager); return myShiroRealm; }*/ @Bean public LoginFilter loginFilter() { return new LoginFilter(); } @Bean public MyFilter myFilter() { return new MyFilter(); } }
ehCahceManager是注冊緩存管理器,MyShiroRealm是權限認證具體的實現,需要注冊到securityManager中,shiroFilter中主要設置過濾器鏈。這里面我主要用到了兩個過濾器,perms和authc。
perms是給訪問URL設置訪問權限的,比如map.put("/RPC52CA3404FDADAB18F91E8210DFCE1522","perms[admin:test]"),key是訪問的URL,value是設置的權限,必須是perms[]的形式。我將需要訪問的接口URL放到數據庫中,在初始化配置的時候通過讀取數據庫將每一個需要鑒權的接口進行權限配置。authc要求必須登錄認證。
由於我們是前后端分離,前端通過RPC接口訪問后端服務,這塊我就想當沒有權限或者沒有登錄時,給前端返回不同的code,所以自己實現了兩個filter,loginFilter替換原來的authc過濾器,myFilter替換原來的perms過濾器。
四、MyFilter和LoginFilter的實現
loginFilter繼承AuthenticationFilter,myFilter繼承PermissionsAuthorizationFilter
public class LoginFilter extends AuthenticationFilter { @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) { HttpServletRequest request = (HttpServletRequest) servletRequest; Subject subject = getSubject(servletRequest,servletResponse); String path = request.getServletPath(); System.out.println("path = " + path); if(path.equals("/RPCAFA2A208FA648EA27C1EC30CADFC8B3D")) { return true; } if(subject.getPrincipals() != null) { return true; } return false; } /** * 會話超時或權限校驗未通過的,統一返回401,由前端頁面彈窗提示 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { ShiroUtil.writeResponse((HttpServletResponse) response,new Result(AuthorizationStatus.NOT_LOGIN));
return false;
}
}
首先會執行isAccessAllowed方法,我將登錄的幾個接口在這里進行排除,直接返回true,就是登錄的接口不需要進行登錄認證。當返回false時執行onAccessDenied方法,這里我直接通過響應流返回給前端json數據。
public class ShiroUtil { /** * 判斷是否需要認證 * @param bean * @return true 不需要 false 需要 */ public static boolean isContains(NotAuthorizationBean bean) { List<String> paths = bean.getPaths(); String name = bean.getName(); for(String path : paths) { if(path.equalsIgnoreCase(name)) { return true; } } return false; } /** * 統一返回前端json數據 * @param response * @param data */ public static void writeResponse(HttpServletResponse response, Object data) { try { response.setContentType("application/json"); OutputStream outputStream = response.getOutputStream(); outputStream.write(JSON.toJSONString(data).getBytes("UTF-8")); outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
public enum AuthorizationStatus { NOT_LOGIN(401,"沒有登錄"), NOT_AUTHORIZATION(403,"沒有授權") ; private Integer code; private String msg; AuthorizationStatus(Integer code, String msg) { this.code = code; this.msg = msg; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
public class Result { private Integer c; private String d; public Result(AuthorizationStatus status) { this.c = status.getCode(); this.d = status.getMsg(); } public Result(Integer c, String d) { this.c = c; this.d = d; } public Integer getC() { return c; } public void setC(Integer c) { this.c = c; } public String getD() { return d; } public void setD(String d) { this.d = d; } }
myFilter的實現類似,也是重寫isAccessAllowed和onAccessDenied兩個方法
public class MyFilter extends PermissionsAuthorizationFilter { @Override public boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws IOException { HttpServletRequest request = (HttpServletRequest) servletRequest; String path = request.getServletPath(); System.out.println("request path = " + path); Subject subject = getSubject(servletRequest,servletResponse); if(path.equals("/RPCAFA2A208FA648EA27C1EC30CADFC8B3D")) { return true; } /* String[] perms = (String[])((String[])o); boolean isPermitted = true; if(perms != null && perms.length > 0) { if(perms.length == 1) { if(!subject.isPermitted(perms[0])) { isPermitted = false; } } else if(!subject.isPermittedAll(perms)) { isPermitted = false; } }*/ return super.isAccessAllowed(servletRequest,servletResponse,o); } /** * 會話超時或權限校驗未通過的,統一返回401,由前端頁面彈窗提示 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { System.out.println("no permission"); Subject subject = getSubject(request,response); if(subject.getPrincipal() == null) { ShiroUtil.writeResponse((HttpServletResponse) response,new Result(AuthorizationStatus.NOT_LOGIN)); }else{ ShiroUtil.writeResponse((HttpServletResponse) response,new Result(AuthorizationStatus.NOT_AUTHORIZATION)); } return false; } }
五、MyShiroRealm實現
realm是權限和登錄管理的具體實現,需要繼承AuthorizingRealm,實現doGetAuthorizationInfo權限認證和doGetAuthenticationInfo登錄認證。
@Service public class MyShiroRealm extends AuthorizingRealm { @Resource private UserInfoService userInfoService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("權限認證doGetAuthorizationInfo()"); String username = (String) super.getAvailablePrincipal(principalCollection); System.out.println("username = " + username); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //simpleAuthorizationInfo.addRole("admin"); simpleAuthorizationInfo.addStringPermission("admin:test"); return simpleAuthorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("登陸認證doGetAuthenticationInfo()"); UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; System.out.println("token = " + token.getUsername()); UserInfo userInfo = userInfoService.findByUsername(token.getUsername()); if(userInfo != null) { return new SimpleAuthenticationInfo(userInfo.getUsername(),userInfo.getPassword(),getName()); } return null; } }