这是我参与更文挑战的第23天,活动详情查看: 更文挑战
使用工具:IDEA,MySQL
数据库表:teacher(用户),role(角色),perms(权限)
teacher表
role表
perms表
shiro是一个安全框架,可以进行认证、授权、密码加密、会话管理
从外部来解析shiro框架:
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
使用:
引入依赖
</dependency>
<!--Shiro整合-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
复制代码
其他依赖
<!--thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.1.1.RELEASE</version>
<!--thymeleaf页面使用shiro标签-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--shiro集成ehcache缓存-->
复制代码
配置类ShiroConfig和自定义Realm
ShiroConfig初始3个bean
//创建ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加Shiro内置过滤器
/*Shiro内置过滤器,实现权限相关的拦截器
*常用过滤器:
* anon:无需认证可以访问
* authc:必须认证才可以访问
* user:如果使用rememberMe功能可以直接访问
* perms:该资源必须得到资源权限才可以访问
* role:给资源必须得到角色权限才可以访问
*
* */
Map<String,String> filterMap = new LinkedHashMap<>();
filterMap.put("/test","anon");
filterMap.put("/login","anon");
filterMap.put("/doRegister","anon");
filterMap.put("/register","anon");
//授权过滤器
//当前授权拦截和,shiro自动跳转到未授权页面
filterMap.put("/add","perms[user:add]");
filterMap.put("/update","perms[user:update]");
filterMap.put("/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//默认跳转到login.jsp,修改为toLogin
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//设置未授权页面
shiroFilterFactoryBean.setUnauthorizedUrl("/unAuth");
return shiroFilterFactoryBean;
}
复制代码
//创建DefaultWebSecurityManager
@Bean("securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联Realm
securityManager.setRealm(userRealm);
//
securityManager.setSessionManager( getDefaultWebSessionManager() );
return securityManager;
}
//创建Realm
@Bean("userRealm")
public UserRealm getRealm(){
return new UserRealm();
}
复制代码
自定义Realm UserRealm.java
继承: extends AuthorizingRealm
重写两个方法
//执行授权逻辑
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权");
return null;
}
//执行认证逻辑
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证");
}
复制代码
Ctrl层
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(name,password);
复制代码
登录
subject.login(token);
登出
subject.logout();
权限管理
认证时获取角色,权限
List<Role> rlist = rMapper.roleList(login.getRid());//获取用户角色
List<Perms> plist = pMapper.permList(login.getRid());//获取用户权限
List<String> roleStrlist=new ArrayList<>();////用户的角色集合
List<String> perminsStrlist=new ArrayList<>();//用户的权限集合
for (Role role : rlist) {
roleStrlist.add(role.getRole_name());
}
for (Perms uPermission : plist) {
perminsStrlist.add(uPermission.getPerm());
}
login.setRoleStrlist(roleStrlist);
login.setPerminsStrlist(perminsStrlist);
授权时添加权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//用户的角色集合
info.addRoles(teacher.getRoleStrlist());
//用户的权限集合
info.addStringPermissions(teacher.getPerminsStrlist());
密码加密
Shiroconfig类中添加两个bean ,定义加密方法,注入
@Bean("hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(1024);// 散列的次数,比如散列两次,相当于md5(md5(""));
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);//是否16进制
return hashedCredentialsMatcher;
}
/**
* 身份认证 realm
*/
@Bean("myShiroRealm")
public UserRealm myShiroRealm(){
UserRealm myShiroRealm = new UserRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
System.out.println("myShiroRealm 注入成功");
return myShiroRealm;
}
Realm认证时
SimpleAuthenticationInfo info = null;
String realname = getName();
// 盐值加密,可以用用户名
ByteSource salt = ByteSource.Util.bytes(tname);
info = new SimpleAuthenticationInfo(login,login.getPassword(),salt, realname);
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("USER_SESSION", login);
盐:增加加密的复杂度,增加hash
复制代码
Shiro中MD5加密方法
ByteSource credentialsSalt01 = ByteSource.Util.bytes("张三");
Object credential = "123456";//密码
String hashAlgorithmName = "MD5";//加密方式
//1024指的是加密的次数
Object simpleHash = new SimpleHash(hashAlgorithmName, credential,
credentialsSalt01, 1024);
System.out.println("加密后的值----->" + simpleHash)
复制代码
缓存机制
为什么要用缓存呢?在页面上访问一些需要角色或权限才能访问的url,你会发现每次访问都去数据库拿对应用户拥有的角色权限,试想一下,我每访问一个url,都要去数据库重复拿这些数据,显然是一个不成熟的做法,如果有了缓存,我们只需要查询一次数据库,之后访问url都将从缓存中拿数据而不是数据库,这样数据库压力明显降低,看起来也相对成熟。
添加依赖
Shiroconfig添加bean
@Bean
public EhCacheManager getCacheManager(){
EhCacheManager ehCacheManager = new EhCacheManager();
//ehCacheManager.setCacheManagerConfigFile("src\\main\\resources\\shiro-ehcache.xml");
return ehCacheManager;
}
添加到securityManager
securityManager.setCacheManager(getCacheManager());
重复登录
Shiroconfig配置
//禁止重复登录
// 配置sessionDAO
@Bean(name="sessionDAO")
public MemorySessionDAO getMemorySessionDAO(){
MemorySessionDAO sessionDAO = new MemorySessionDAO();
return sessionDAO;
}
//配置shiro session 的一个管理器
@Bean(name = "sessionManager")
public DefaultWebSessionManager getDefaultWebSessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 设置session过期时间
sessionManager.setGlobalSessionTimeout(60*60*1000);
// 请注意看代码
sessionManager.setSessionDAO(getMemorySessionDAO());
//解决url携带jessionid问题
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
Realm认证时判断删除session
//禁止同时登陆
if( tname.equals( login.getName() ) && md5.equals( login.getPassword() ) ){
// 获取所有session
Collection<Session> sessions = sessionDAO.getActiveSessions();
for (Session session: sessions) {
Teacher sysUser = (Teacher) session.getAttribute("USER_SESSION");
// 如果session里面有当前登陆的,则证明是重复登陆的,则将其剔除
System.out.println("seesion:"+session.getId());
if( sysUser!=null ){
if( tname.equals( sysUser.getName() ) ){
session.setTimeout(0);
}
}
}
}
复制代码
其他
//配置ShiroDialect,用于thymeleaf和shiro标签配合使用
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
复制代码