Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、受权、密码和会话管理。html
shiro有个核心组件,分别为Subject、SecurityManager和Realmsjava
若是想要更加深刻的了解的shrio能够参考:https://www.w3cschool.cn/shiro/co4m1if2.htmlgit
<!--shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.7.1</version> </dependency> <!--shiro整合thymeleaf--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <!--shiro缓存--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.7.1</version> </dependency>
shiro默认是与jsp进行使用的,而这里是shiro整合thymeleaf全部要导入shiro整合thymeleaf的jar包github
通常来讲整合只须要完成两个类的实现便可
一个是 ShiroConfig 一个是 CustomerRealm
若是须要添加shiro缓存而且不是自带的缓存而是redis缓存还须要进行另外两个类的编写
一个是 RedisCache 一个是 RedisCacheManagerweb
未加shiro的缓存redis
package com.yuwen.config; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import com.yuwen.shiro.cache.RedisCacheManager; import com.yuwen.shiro.realm.CustomerRealm; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration public class ShiroConfig { //让页面shiro标签生效 @Bean public ShiroDialect shiroDialect(){ return new ShiroDialect(); } //一、建立shiroFilter 负责拦截全部请求 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); //给filter设置安全管理 factoryBean.setSecurityManager(defaultWebSecurityManager); //配置系统的受限资源 //配置系统公共资源 所有都能访问的设置anon Map<String,String> map = new HashMap<>(); map.put("/main","authc");//请求这个资源须要认证和受权 authc表示须要认证后才能访问 map.put("/admin","roles[admin]"); //表示admin角色才能访问 roles[]表示须要什么角色才能访问 map.put("/manage","perms[user:*:*]"); //表示须要user:*:*权限才能访问 perms[]表示须要什么权限才能访问 //访问须要认证的页面若是未登陆会跳转到/login路由进行登录 factoryBean.setLoginUrl("/login"); //访问未受权页面会自动跳转到/unAuth路由 factoryBean.setUnauthorizedUrl("/unAuth"); factoryBean.setFilterChainDefinitionMap(map); return factoryBean; } //二、建立安全管理器 @Bean public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("getRealm") Realm realm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //给安全管理器设置 securityManager.setRealm(realm); return securityManager; } //三、建立自定义的realm @Bean public Realm getRealm(){ CustomerRealm customerRealm = new CustomerRealm(); //修改凭证校验匹配器 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //设置加密算法为md5 credentialsMatcher.setHashAlgorithmName("MD5"); //设置散列次数 credentialsMatcher.setHashIterations(1024); customerRealm.setCredentialsMatcher(credentialsMatcher); return customerRealm; } }
由于通常在数据库中设置明文密码不安全,全部我这里对密码进行了md5加密,个人加密方式为:密码 = 密码+盐+散列次数 然后进行MD5加密 因此这里建立自定义的realm时须要进行设置匹配器这样登陆时密码才能匹配成功算法
package com.yuwen.shiro.realm; import com.yuwen.pojo.User; import com.yuwen.pojo.vo.ViewPerms; import com.yuwen.pojo.vo.ViewRole; import com.yuwen.service.UserService; import com.yuwen.shiro.salt.MyByteSource; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.CollectionUtils; import org.springframework.util.ObjectUtils; import javax.annotation.Resource; import java.util.List; //自定义realm public class CustomerRealm extends AuthorizingRealm { @Resource private UserService userService; //受权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取身份信息 String primaryPrincipal = (String)principalCollection.getPrimaryPrincipal(); //根据主身份信息获取角色 和 权限信息 List<ViewRole> roles = userService.findRolesByUsername(primaryPrincipal); if (!CollectionUtils.isEmpty(roles)){ SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); roles.forEach(viewRole -> { simpleAuthorizationInfo.addRole(viewRole.getName()); //权限信息 List<ViewPerms> perms = userService.findPermsByRoleId(viewRole.getName()); if (!CollectionUtils.isEmpty(perms)){ perms.forEach(viewPerms -> { simpleAuthorizationInfo.addStringPermission(viewPerms.getPName()); }); } }); return simpleAuthorizationInfo; } return null; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //获取登入的身份信息 String principal = (String) authenticationToken.getPrincipal(); User user = userService.findByUsername(principal); if (!ObjectUtils.isEmpty(user)){ //ByteSource.Util.bytes(user.getSalt()) 经过这个工具将盐传入 //若是身份认证验证成功,返回一个AuthenticationInfo实现; return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),new MyByteSource(user.getSalt()),this.getName()); } return null; } }
在登陆时会自动调用这个身份验证 在验证时若是出错,会报异常,我在controller层接收了异常并处理spring
controller层中登陆时的异常处理 数据库
@PostMapping("/login") public String login(String username,String password){ //获取主体对象 Subject subject = SecurityUtils.getSubject(); try { //自动调用CustomerRealm 类中的身份验证方法 subject.login(new UsernamePasswordToken(username,password)); return "index"; }catch (UnknownAccountException e){ //接收异常并处理 e.printStackTrace(); model.addAttribute("msg","用户名有误,请从新登陆"); }catch (IncorrectCredentialsException e){//接收异常并处理 e.printStackTrace(); model.addAttribute("msg","密码有误,请从新登陆"); } return "login"; }
定义了shiro缓存,用户登陆后,其用户信息、拥有的角色 / 权限没必要每次去查,这样能够提升效率apache
默认缓存的配置
在 ShiroConfig中 的 getRealm() 方法中开启缓存管理
@Bean public Realm getRealm(){ CustomerRealm customerRealm = new CustomerRealm(); //开启缓存管理 customerRealm.setCacheManager(new EhCacheManager()); //开启全局缓存 customerRealm.setCachingEnabled(true); //开启认证缓存 customerRealm.setAuthenticationCachingEnabled(true); customerRealm.setAuthenticationCacheName("authenticationCache"); //开启权限缓存 customerRealm.setAuthorizationCachingEnabled(true); customerRealm.setAuthorizationCacheName("authorizationCache"); return customerRealm; }
与reids整合的缓存这里就不说明了,放在源码里本身查看,源码在下方
在这里用标签来判断某些区域须要认证或什么角色或者什么权限才能访问
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>首页</title> <link rel="shortcut icon" href="#"> </head> <body> <h1>index</h1> <a href="/logout">退出</a> <div> <a href="/main">main</a> | <a href="/manage">manage</a> | <a href="/admin">admin</a> </div> <!--获取认证信息--> 用户:<span shiro:principal=""></span><hr> <!--认证处理--> <span shiro:authenticated=""><hr> 显示认证经过内容 </span> <span shiro:notAuthenticated=""><hr> 没有认证时 显示 </span> <!--受权角色--> <span shiro:hasRole="admin"><hr> admin角色 显示 </span> <span shiro:hasPermission="user:*:*"><hr> 具备用户模块的"user:*:*"权限 显示 </span> </body> </html>