Shiro简介
Apache Shiro 是 Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在JavaEE 环境。Shiro 可以帮助我们完成,认证、授权、加密、会话管理、与Web集成、缓存等。
Shiro的主要框架图:
Shiro主要组成简单说明:
Subject
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
SecurityManager
SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
Authenticator
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
Authorizer
Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
realm
Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
SessionManager
sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
SessionDAO
SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。
CacheManager
CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。
Cryptography
Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
Springboot2.0整合Shiro实践
该实践项目选用Springboot2.0 、Jpa 、Ehcache 、Swagger2、Fastjson、Shiro。
项目地址:https://github.com/wlcloudy/springboot-shiro
Shiro配置
配置拦截器,定义拦截URL权限
@Bean
public ShiroFilterFactoryBean shirFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map filterChainDefinitionMap = new LinkedHashMap();
// 静态资源匿名访问
filterChainDefinitionMap.put("/static/**", "anon");
// Swagger
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/v2/api-docs", "anon");
// 登录匿名访问
filterChainDefinitionMap.put("/api/v1/login", "anon");
filterChainDefinitionMap.put("/api/v1/logout", "logout");
// 其他路径均需要身份认证,一般位于最下面,优先级最低
filterChainDefinitionMap.put("/api/v1/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 登录的路径
// shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后跳转的路径
// shiroFilterFactoryBean.setSuccessUrl("/index");
// 验证失败后跳转的路径
// shiroFilterFactoryBean.setUnauthorizedUrl("/403");
Map filters = new LinkedHashMap();
JsonFilter jsonFilter = new JsonFilter();
filters.put("login", jsonFilter);
shiroFilterFactoryBean.setFilters(filters);
return shiroFilterFactoryBean;
}
密码匹配器,主要是定义密码匹配规则,可以自定义。如下图默认将密码明文MD5两次进行匹配。
// 默认实现
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new MyHashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
// 自定义实现
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
MyHashedCredentialsMatcher myHashedCredentialsMatcher = new MyHashedCredentialsMatcher();
myHashedCredentialsMatcher.setHashAlgorithmName("MD5");
myHashedCredentialsMatcher.setHashIterations(2);
return myHashedCredentialsMatcher;
}
@Component
public class MyHashedCredentialsMatcher extends HashedCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//如果是微信登录方式
if (WeChatToken.class.isAssignableFrom(token.getClass())) {
return true;
}
return super.doCredentialsMatch(token, info);
}
}
缓存管理器,本例集成的是Ehcache缓存实现
@Bean
public EhCacheManager ehCacheManager(CacheManager cacheManager) {
EhCacheManager em = new EhCacheManager();
//将ehcacheManager转换成shiro包装后的ehcacheManager对象
em.setCacheManager(cacheManager);
return em;
}
session管理器
/**
* sessionDAO
* @return
*/
@Bean
public SessionDAO sessionDAO(){
return new MemorySessionDAO();
}
/**
* cookie信息
* @return
*/
@Bean
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
simpleCookie.setHttpOnly(true);
return simpleCookie;
}
/**
* session管理器
* @param ehCacheManager
* @return
*/
@Bean
public SessionManager sessionManager(EhCacheManager ehCacheManager) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(tomcatTimeout * 1000);
sessionManager.setSessionDAO(sessionDAO());
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionIdCookie(simpleCookie());
sessionManager.setCacheManager(ehCacheManager);
return sessionManager;
}
Realm,主要用作认证及授权
/**
* 自定义Realm,可以多个
*/
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
@Slf4j
public class ShiroRealm extends AuthorizingRealm {
@Autowired
@Lazy
private UserRepository userRepository;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken || token instanceof WeChatToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("授权");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
log.info("身份认证");
// 获取用户登录账号
String username = (String) token.getPrincipal();
User userInfo = userRepository.findUserByUsername(username);
if (null ==userInfo){
return null;
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userInfo, userInfo.getPassword(), ByteSource.Util.bytes(userInfo.getUsername()), getName());
return info;
}
}
SecurityManager 安全管理器,Shiro的核心
@Bean
public DefaultWebSecurityManager securityManager(EhCacheManager ehCacheManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
securityManager.setCacheManager(ehCacheManager);
return securityManager;
}
登录控制器LoginController
@Api(tags = "登录/登出接口")
@RestController
@RequestMapping(Endpoints.API_V1)
public class LoginController{
@ApiOperation("用户登录")
@PostMapping("/login")
public JsonResult login(String username, String password)
{
/*
1、验证用户是否已经登录
2.1、已登录则直接返回
2.2、未登录则登录验证
*/
Subject currentUser = SecurityUtils.getSubject();
User user = (User) currentUser.getPrincipal();
if(user != null) {
if (!StringUtils.equals(user.getEmail(),username)){
currentUser.logout();
}else {
return JsonResult.OK(ResultCode.SUCCESS.code());
}
}
try {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
currentUser.login(token);
return JsonResult.OK();
}catch( UnknownAccountException uae ) {
return JsonResult.Err(ResultCode.UNKNOWN_ACCOUNT);
} catch ( IncorrectCredentialsException ice ) {
return JsonResult.Err(ResultCode.INCORRECT_CREDENTIALS);
} catch ( LockedAccountException lae ) {
return JsonResult.Err(ResultCode.LOCKED_ACCOUNT);
} catch ( ExcessiveAttemptsException eae ) {
return JsonResult.Err(ResultCode.EXCESSIVE_ATTEMPTS);
} catch ( AuthenticationException ae ) {
return JsonResult.Err(ResultCode.LOGIN_FAIL);
}
}
@ApiOperation("用户登出")
@GetMapping(Endpoints.LOGOUT)
public JsonResult logout()
{
Subject currentUser = SecurityUtils.getSubject();
Object principal = currentUser.getPrincipal();
if (principal != null) {
currentUser.logout();
}
return JsonResult.OK();
}
}
本文主要简单介绍了SpringBoot2.0如何将Shiro整合在一起并实现简单的登录验证,后面还将对授权等做进一步讲解。如果有兴趣的朋友想更加细致的了解Shiro可以去看看《跟我学Shiro》张开涛 对整个框架的使用有一定的帮助。
参考文章: