springboot2整合shiro
阿新 • • 發佈:2018-11-03
0.準備
SQL使用flyway管理, DAO層是用mybatis, 快取使用Redis, 具體看原始碼(本章末尾會貼出).下面主要是shiro部分
1.新增Maven依賴
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
2.自定義SpringbootDemoAuthorizingRealm繼承AuthorizingRealm
public class SpringbootDemoAuthorizingRealm extends AuthorizingRealm { @Autowired private IUserService userServiceImpl; @Autowired private IRoleService roleServiceImpl; @Autowired private IPermissionService permissionServiceImpl; @Override public String getName() { return "SpringbootDemoAuthorizingRealm"; } // 認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); String password = new String(token.getPassword()); if (StringUtils.isBlank(username)) { throw new UnknownAccountException(); } if (StringUtils.isBlank(password)) { throw new IncorrectCredentialsException(); } User user = userServiceImpl.getUserByUsername(username); if (Objects.isNull(user)) { throw new UnknownAccountException(); } SimpleUser simpleUser = new SimpleUser(); simpleUser.setId(user.getId()); simpleUser.setUsername(user.getUsername()); // 清除授權資訊 clearAuthorizationInfoCache(simpleUser); return new SimpleAuthenticationInfo(simpleUser, user.getPassword(), ByteSource.Util.bytes(username), this.getName()); } // 授權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleUser simpleUser = (SimpleUser) principalCollection.getPrimaryPrincipal(); List<Role> roleList = roleServiceImpl.getRoleListByUserId(simpleUser.getId()); List<String> simpleRoles = new ArrayList<>(roleList.size()); List<Long> roleIds = new ArrayList<>(roleList.size()); for (Role role : roleList) { simpleRoles.add(role.getRole()); roleIds.add(role.getId()); } List<String> permissions = permissionServiceImpl.getPermissionsByRoleIds(roleIds); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addRoles(simpleRoles); simpleAuthorizationInfo.addStringPermissions(permissions); return simpleAuthorizationInfo; } /** * 清除所有授權資訊 */ public void clearAuthorizationInfoCache() { Cache<Object, AuthorizationInfo> cache = getAuthorizationCache(); if(Objects.nonNull(cache)) { cache.clear(); } } private void clearAuthorizationInfoCache(SimpleUser simpleUser) { Cache<Object, AuthorizationInfo> cache = getAuthorizationCache(); cache.remove(simpleUser.getId()); } }
3.自定義ShiroConfig
@Configuration public class ShiroConfig { @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5"); hashedCredentialsMatcher.setHashIterations(3); return hashedCredentialsMatcher; } @Bean public SpringbootDemoAuthorizingRealm springbootDemoAuthorizingRealm(HashedCredentialsMatcher hashedCredentialsMatcher) { SpringbootDemoAuthorizingRealm realm = new SpringbootDemoAuthorizingRealm(); realm.setCredentialsMatcher(hashedCredentialsMatcher); // 啟用快取 realm.setCachingEnabled(true); // 啟用認證快取 realm.setAuthorizationCachingEnabled(true); // 啟用授權快取 realm.setAuthorizationCachingEnabled(true); return realm; } @Bean public SecurityManager securityManager(SpringbootDemoAuthorizingRealm springbootDemoAuthorizingRealm, SessionManager sessionManager, ShiroCacheManager shiroCacheManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(springbootDemoAuthorizingRealm); securityManager.setSessionManager(sessionManager); securityManager.setCacheManager(shiroCacheManager); return securityManager; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/"); shiroFilterFactoryBean.setUnauthorizedUrl("/forbidden.do"); Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>(); // 登入不攔截 filterChainDefinitionMap.put("/login.do", "anon"); // 其他介面都必須登入 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public SessionManager sessionManager() { DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager(); // 跳轉頁面刪除sessionId defaultWebSessionManager.setSessionIdUrlRewritingEnabled(false); // 30分鐘 defaultWebSessionManager.setGlobalSessionTimeout(1800000); defaultWebSessionManager.setDeleteInvalidSessions(true); defaultWebSessionManager.setSessionValidationSchedulerEnabled(true); defaultWebSessionManager.setSessionIdCookieEnabled(true); return defaultWebSessionManager; } }
4.自定義ShiroCacheManager, 使用Redis作為Shiro快取
@Component
public class ShiroCacheManager implements CacheManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
return new ShiroRedisCache<>(name);
}
public class ShiroRedisCache<K,V> implements Cache<K,V> {
private String cacheKey;
private ShiroRedisCache(String cacheKey) {
this.cacheKey = cacheKey;
}
private Object hashKey(K key) {
if(key instanceof PrincipalCollection) {
PrincipalCollection principalCollection = (PrincipalCollection) key;
SimpleUser simpleUser = (SimpleUser) principalCollection.getPrimaryPrincipal();
return simpleUser.getId();
}
return key;
}
@Override
public V get(K key) throws CacheException {
BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
Object realKey = hashKey(key);
return boundHashOperations.get(realKey);
}
@Override
public V put(K key, V value) throws CacheException {
BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
Object realKey = hashKey(key);
boundHashOperations.put((K) realKey, value);
return value;
}
@Override
public V remove(K key) throws CacheException {
BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
Object realKey = hashKey(key);
V value = boundHashOperations.get(realKey);
boundHashOperations.delete(realKey);
return value;
}
@Override
public void clear() throws CacheException {
redisTemplate.delete(cacheKey);
}
@Override
public int size() {
BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
return boundHashOperations.size().intValue();
}
@Override
public Set<K> keys() {
BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
return boundHashOperations.keys();
}
@Override
public Collection<V> values() {
BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
return boundHashOperations.values();
}
}
}
5.使用
@RestController
public class LoginController {
@RequestMapping(value="", method = RequestMethod.GET)
public ResultMessage defaultPath(){
return ResultMessage.fail("請先登入");
}
@RequestMapping(value="/forbidden.do", method = RequestMethod.GET)
public ResultMessage forbidden(){
return ResultMessage.fail("沒有許可權");
}
@RequestMapping(value="/login.do", method = RequestMethod.POST)
public ResultMessage login(@RequestBody UserRequest request){
UsernamePasswordToken token = new UsernamePasswordToken(request.getUsername(), request.getPassword(),false);
Subject currentUser = SecurityUtils.getSubject();
try {
currentUser.login(token);
} catch (UnknownAccountException e) {
throw new UnknownAccountException("賬號不存在");
} catch (IncorrectCredentialsException e) {
throw new IncorrectCredentialsException("密碼不匹配");
} catch (LockedAccountException e) {
throw new LockedAccountException("賬號被鎖定");
} catch (AuthenticationException e) {
throw new AuthenticationException("其他認證錯誤");
} catch (RuntimeException e) {
throw new RuntimeException("登入未知錯誤");
}
return ResultMessage.success("登入成功");
}
@RequestMapping(value="/logout.do", method =RequestMethod.GET)
public ResultMessage logout(){
try {
SecurityUtils.getSubject().logout();
} catch (Exception e) {
throw new RuntimeException("登出未知錯誤");
}
return ResultMessage.success("登出成功");
}
@RequestMapping(value="/other.do", method =RequestMethod.GET)
public ResultMessage other(){
return ResultMessage.success("other success.");
}
@RequestMapping(value="/addTeacher.do", method =RequestMethod.GET)
@RequiresPermissions({"teacher:add"})
public ResultMessage addTeacher(){
return ResultMessage.success("permission success.");
}
@RequestMapping(value="/updateTeacher.do", method =RequestMethod.GET)
@RequiresPermissions({"teacher:update"})
public ResultMessage updateTeacher(){
return ResultMessage.success("permission success.");
}
/**
* 統一異常處理
*/
@ExceptionHandler(RuntimeException.class)
public ResultMessage exception(Exception ex) {
if (ex instanceof AuthorizationException) {
return ResultMessage.fail("未授權");
}
return ResultMessage.fail(ex.getMessage());
}
}
6.總結
shiro相對比較簡單, 認證(可以理解為登入), 授權(就是許可權判斷), 會話管理, 加密等模組.
下一節會講如何重寫註解RequiresPermissions, 並將選單入庫.
原始碼 https://gitee.com/jsjack_wang/springboot-demo dev-shiro分支