虽然,直接用Spring Security和SpringBoot 进行“全家桶式”的合作是最好不过的,但现实总是欺负我们这些没办法决定架构类型的娃子。
Apache Shiro 也有其特殊之处滴。若需了解,可以转战到[Apache Shiro 简介]
shiro的版本,看个人喜好哈,本文的版本为:
<shiro.version>1.3.2</shiro.version> 复制代码
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-aspectj</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-quartz</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> 复制代码
授权认证具体实现之地。通过继承 AuthorizingRealm 进而实现,对登录时的账号密码校验功能
@Slf4j public class ShiroRealm extends AuthorizingRealm { @Autowired private ShiroPermissionRepository shiroPermissionRepository; /** * 授权 * * @param principalCollection 主要信息 * @return 授权信息 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { if (log.isInfoEnabled()){ log.info("Authorization begin"); } String name= (String) principalCollection.getPrimaryPrincipal(); List<String> role = shiroPermissionRepository.queryRoleByName(name); if (role.isEmpty()){ SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addRoles(role); return simpleAuthorizationInfo; } return null; } /** * 认证 * * @param authenticationToken 认证token * @return 认证结果 * @throws AuthenticationException 认证异常 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { if (log.isInfoEnabled()){ log.info("Authentication begin"); } UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; Object principal =token.getPrincipal(); Object credentials = token.getCredentials(); //校验用户名 checkBlank(principal,"用户名不能为空"); //校验密码 checkBlank(credentials,"密码不能为空"); //校验姓名 String username = (String) principal; UserPO userPO = shiroPermissionRepository.findAllByName(username); if (userPO == null){ throw new AccountException("用户名错误"); } //校验密码 String password = (String) credentials; if (!StringUtils.equals(password,userPO.getPassword())){ throw new AccountException("密码错误"); } return new SimpleAuthenticationInfo(principal, password, getName()); } private void checkBlank(Object obj,String message){ if (obj instanceof String){ if (StringUtils.isBlank((String) obj)){ throw new AccountException(message); } }else if (obj == null){ throw new AccountException(message); } } } 复制代码
将ShiroConfig、SecurityManager、ShiroFilterFactoryBean交给Spring管理.
@Configuration public class ShiroConfig { private final static String AUTHC_STR = "authc"; private final static String ANON_STR = "anon"; /** * 验证授权、认证 * * @return shiroRealm 授权认证 */ @Bean public ShiroRealm shiroRealm(){ return new ShiroRealm(); } /** * session manager * * @param shiroRealm 授权认证 * @return 安全管理 */ @Bean @ConditionalOnClass(ShiroRealm.class) public SecurityManager securityManager(ShiroRealm shiroRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(shiroRealm); return securityManager; } /** * Filter工厂,设置对应的过滤条件和跳转条件 * * @param securityManager session 管理 * @return shiro 过滤工厂 */ @Bean @ConditionalOnClass(value = {SecurityManager.class}) public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setFilters(filterMap); //URI过滤 Map<String,String> map = Maps.newLinkedHashMap(); //可过滤的接口路径 //所有API路径进行校验 map.put("/api/**",AUTHC_STR); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } } 复制代码
shiro和security也有相似之处,都有自己的 filter chain。翻一番Shiro的源码,追溯一下。发下以下:
protected FilterChainManager createFilterChainManager() { DefaultFilterChainManager manager = new DefaultFilterChainManager(); Map<String, Filter> defaultFilters = manager.getFilters(); //apply global settings if necessary: for (Filter filter : defaultFilters.values()) { applyGlobalPropertiesIfNecessary(filter); } //Apply the acquired and/or configured filters: Map<String, Filter> filters = getFilters(); if (!CollectionUtils.isEmpty(filters)) { for (Map.Entry<String, Filter> entry : filters.entrySet()) { String name = entry.getKey(); Filter filter = entry.getValue(); applyGlobalPropertiesIfNecessary(filter); if (filter instanceof Nameable) { ((Nameable) filter).setName(name); } //'init' argument is false, since Spring-configured filters should be initialized //in Spring (i.e. 'init-method=blah') or implement InitializingBean: manager.addFilter(name, filter, false); } } //build up the chains: Map<String, String> chains = getFilterChainDefinitionMap(); if (!CollectionUtils.isEmpty(chains)) { for (Map.Entry<String, String> entry : chains.entrySet()) { String url = entry.getKey(); String chainDefinition = entry.getValue(); manager.createChain(url, chainDefinition); } } return manager; } 复制代码
从源码可以发现,shiro的过滤器链,添加顺序是:
这里咱看看DefaultFilterChainManager 到底添加了那些默认过滤器链,可以看到主要的是:DefaultFilter
protected void addDefaultFilters(boolean init) { for (DefaultFilter defaultFilter : DefaultFilter.values()) { addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false); } } 复制代码
anon(AnonymousFilter.class), authc(FormAuthenticationFilter.class), authcBasic(BasicHttpAuthenticationFilter.class), logout(LogoutFilter.class), noSessionCreation(NoSessionCreationFilter.class), perms(PermissionsAuthorizationFilter.class), port(PortFilter.class), rest(HttpMethodPermissionFilter.class), roles(RolesAuthorizationFilter.class), ssl(SslFilter.class), user(UserFilter.class); 复制代码
由于设置对全局接口进行校验,因此,预期结果就是不能够访问啦
map.put("/api/**",AUTHC_STR); 复制代码
@RestController @RequestMapping( SYSTEM_API +"shiro") public class ShiroIdal { @Resource private IShiroService iShiroService; @GetMapping public HttpEntity obtain(@RequestParam String name){ return iShiroService.obtainUserByName(name); } } 复制代码
@Slf4j @Service public class ShiroServiceImpl implements IShiroService { @Resource private ShiroPermissionRepository shiroPermissionRepository; public HttpEntity obtainUserByName(String name) { UserPO userPO = shiroPermissionRepository.findAllByName(name); return HttpResponseSupport.success(userPO); } } 复制代码
在URI过滤Map加入以下:
map.put("/api/shiro",ANON_STR); 复制代码
注意:要在“全局Api劫持”前添加。而且不要使用“HashMap”,为什么?
在说为什么前,先了解HashMap这货是什么原理先。
for (Entry<String, String> entry : hashMap.entrySet()) { MessageFormat.format("{0}={1}",entry.getKey(),entry.getValue()); } 复制代码
这会影响shiro哪里呢?
Map<String, String> chains = getFilterChainDefinitionMap(); if (!CollectionUtils.isEmpty(chains)) { for (Map.Entry<String, String> entry : chains.entrySet()) { String url = entry.getKey(); String chainDefinition = entry.getValue(); manager.createChain(url, chainDefinition); } } 复制代码
ShiroFilterFactoryBean 中,在构建shiro的filter chain时,会对我们配置的FilterChainDefinitionMap 进行一次遍历,并且将其添加到DefaultFilterChainManager中。
设想以下,若“全局API劫持”在最前面,那么只要在/api/*裆下的,都早早被劫持了。轮得到配置的 anon 么?若由于HashMap的散列排序导致“全局API劫持”在最前面,emmmm,那玩锤子。
因此,建议使用LinkedHashMap,为啥子?撸源码
static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } transient LinkedHashMap.Entry<K,V> head; transient LinkedHashMap.Entry<K,V> tail; 复制代码
内部类中多了两个Entry,一个记录前方entry,一个记录后方entry,这样的双向链表结构保证了插入顺序的有序。
有点跑偏了,这些大伙肯定都知道滴了......