雖然,直接用Spring Security和SpringBoot 進行「全家桶式」的合做是最好不過的,但現實老是欺負咱們這些沒辦法決定架構類型的娃子。java
Apache Shiro 也有其特殊之處滴。若需瞭解,能夠轉戰到[Apache Shiro 簡介]web
shiro的版本,看我的喜愛哈,本文的版本爲:spring
<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 進而實現,對登陸時的帳號密碼校驗功能apache
@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管理.c#
@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的過濾器鏈,添加順序是:api
這裏咱看看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);
}
}
複製代碼
若沒 login.jsp,則會直接報錯,我的以爲太不和諧了,畢竟如今都是先後端分離的。session
在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());
}
複製代碼
HashMap散列圖是按「有利於隨機查找的散列(hash)的順序」。並不是按輸入順序。遍歷時只能所有輸出,而沒有順序。甚至能夠rehash()從新散列,來得到更利於隨機存取的內部順序。
這會影響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,這樣的雙向鏈表結構保證了插入順序的有序。
LinkedHashMap底層是數組加單項鍊表加雙向鏈表 。
有點跑偏了,這些大夥確定都知道滴了......