Springboot集成权限管理框架apache shiro
一、名词解释
网上一大堆
二、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; } }