参考博客:
https://blog.csdn.net/aimashi620/article/details/80880007
https://blog.csdn.net/pengjwhx/article/details/84867112?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4
https://blog.csdn.net/weixin_30263277/article/details/95053586?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-26&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-26
https://blog.csdn.net/qq_41717874/article/details/84989988?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1
1.shiro功能
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。
主要功能
Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份,通常用来做登录验证的;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web 支持,可以非常容易的集成到 Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
2 Shiro的架构
可以看到:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject;
Subject:主体,代表了当前 “用户”
SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;
Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
最简单的一个 Shiro 应用:
应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
3 身份验证
在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名 / 密码 / 手机号。
credentials:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。
流程如下:
1) 首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils.setSecurityManager() 设置;
2 )SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
3 )Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
4 )Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
5 ) Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。
4 maven相关依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.0</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</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-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>${shiro.version}</version>
</dependency>
5 Realm
自定义realm一般继承 AuthorizingRealm(授权)即可,重写里面AuthenticationInfo 认证和doGetAuthorizationInfo授权方法。
6 用户登录demo
一、用户认证
1 Subject currentUser = SecurityUtils.getSubject();
2 UsernamePasswordToken token = new UsernamePasswordToken(username, password.toCharArray());
3 currentUser.login(token);
@Slf4j
@RestController
public class LoginController {
@PostMapping("/login")
@ResponseBody
public Object loginPost(String username, String password) {
if (StringUtils.isBlank(username)) {
return renderError("用户名不能为空");
}
if (StringUtils.isBlank(password)) {
return renderError("密码不能为空");
}
//Shiro加密
// UserRealm userRealm = new UserRealm();
// HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// matcher.setHashAlgorithmName("md5");//设置加密算法名称
// matcher.setHashIterations(1);//设置加密的次数
// userRealm.setCredentialsMatcher(matcher);
Subject user = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
Result result=new Result();
try {
//在这一步跳入入自己实现的域即shiroDbRealm中验证
user.login(token);
result.setMsg("登录成功");
return result;
} catch (UnknownAccountException e) {
result.setMsg("账号不存在");
return result;
} catch (DisabledAccountException e) {
result.setMsg("账号未启用");
return result;
} catch (IncorrectCredentialsException e) {
result.setMsg("密码错误,请重试");
return result;
} catch (Throwable e) {
result.setMsg("未知错误,请联系管理员");
return result;
}
}
}
二、自定义Realm
@Slf4j
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private AuthorityService authorityService;
/**
* 权限认证
* 取得当前用户权限
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//取得当前用户权限
String permission= this.authorityService.getPermissionsForCurrentUser();
Set<String> permissions = new HashSet<>();
for(String element : permission.split(";")) {
permissions.add(element);
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissions);
return info;
}
/**
* 登录认证
* 验证用户输入到用户密码是否正确
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//第一种方式 获取用户的输入的账号.
// UsernamePasswordToken mytoken=(UsernamePasswordToken) token;
// String username=mytoken.getUsername();
// String password = mytoken.getPassword().toString();
//第二种 获取用户的输入的账号.
String username = (String)token.getPrincipal();
String password = new String((char[]) token.getCredentials());
//根据用户名称查询数据库
UserEntity userEntity = userService.queryUserByName(username);
//账号不存在
if(userEntity == null) {
//用户不存在
return null;
}
//如果能查询到,再由框架比对数据库中查询到的密码和页面提交的密码是否一致
return new SimpleAuthenticationInfo(userEntity, password, getName());
}
}
三、Shiro配置
@Configuration
@Slf4j
public class ShiroConfig {
@Bean
public FilterRegistrationBean delegatingFilterProxy(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
DelegatingFilterProxy proxy = new DelegatingFilterProxy();
proxy.setTargetFilterLifecycle(true);
proxy.setTargetBeanName("shiroFilter");
filterRegistrationBean.setFilter(proxy);
return filterRegistrationBean;
}
/**
* Shiro的过滤器链
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
/**
* 配置shiro拦截器链
* anon 不需要认证
* authc 需要认证
* 顺序从上到下,优先级依次降低
*
*/
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
filterChainDefinitionMap.put("/user/logind", "anon");
filterChainDefinitionMap.put("/user/logoutd", "anon");
filterChainDefinitionMap.put("/hive/sql", "anon");
filterChainDefinitionMap.put("/cluster/host/services/**", "anon");
filterChainDefinitionMap.put("/**.png", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/**.js", "anon");
filterChainDefinitionMap.put("/**.jpg", "anon");
filterChainDefinitionMap.put("/**.less", "anon");
filterChainDefinitionMap.put("/**.css", "anon");
filterChainDefinitionMap.put("/index.html", "anon");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/public/**", "anon");
filterChainDefinitionMap.put("/**", "login");
// 默认的登陆访问url,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/unauth");
//没有权限跳转的url
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilterFactoryBean.getFilters().put("login", new ShiroLoginFilter());
shiroFilterFactoryBean.getFilters().put("perms", new ShiroPermsFilter());
return shiroFilterFactoryBean;
}
/**
* 项目自定义的Realm
*/
@Bean
public UserRealm myShiroRealm(){
return new UserRealm();
}
/**
* 安全管理器
* @return
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
securityManager.setCacheManager(ehCacheManager());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 设置session超时时间
* @return
*/
@Bean
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(60*60*1000); //一个小时
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
/**
* 记住密码Cookie
* 新增
*/
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setHttpOnly(true);
simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天
return simpleCookie;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param
* @return
*/
@Bean
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;
}
/**
* shiro缓存管理器
*
* @return
*/
@Bean
public EhCacheManager ehCacheManager(){
log.info("ShiroConfig.getEhCacheManager()");
EhCacheManager cacheManager = new EhCacheManager();
//classpath:org/apache/shiro/cache/ehcache/ehcache.xml
cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return cacheManager;
}
}
7 Shiro常用的方法
得到 Subject 及创建用户名/密码身份验证 Token(即用户身份/凭证)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
登录,即身份验证
subject.login(token);
判断用户是否经过验证
subject.isAuthenticated()
退出
subject.logout();
检查用户是否有admin 角色
subject.checkRole("role1")
subject.hasRole("role1")
subject.checkRoles("admin");
subject.hasAllRoles(Arrays.asList("role1", "role2"))
检查用户是否有user:delete权限
subject.isPermitted("user:create")
subject.isPermittedAll("user:update", "user:delete")
subject.checkPermission("user:create");
subject.checkPermissions("user:delete", "user:update");