shiro介绍
shiro是apache的一款开源安全管理框架,帮助开发者进行身份验证,授权等安全管理。
优点:1、shiro提供一整套security对象身份信息验证,授权方法,且提供内部验证机制,使身份信息保密验证,更具有安全性。
2、shiro提供一套资源控制机制,开发者可自定义资源访问权限和访问条件。
一、步骤
1、导包
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.6.0</version>
</dependency>
在pom.xml文件中导入上面依赖,即可使用shiro框架。
2、书写配置类
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.*;
@Configuration
public class ShiroConfig {
//1、Realm 代表系统资源
// @Bean
// public Realm getRealm(){
// return new MyShiroRealm();
// }
//2、创建SecurityManager用户管理中心 用于流程控制
@Bean
public DefaultWebSecurityManager getSecurityManager(AuthorizingRealm myShiroRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//创建验证器,会将前端传递的密码通过下方设置的加密方法和迭代次数进行加密后验证
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//设置shiro加密算法和迭代次数
matcher.setHashAlgorithmName("MD5");
matcher.setHashIterations(3);
//设置密码校验器
myShiroRealm.setCredentialsMatcher(matcher);
securityManager.setRealm(myShiroRealm);
return securityManager;
}
//3、创建监听器工厂bean 请求过滤器
@Bean
public ShiroFilterFactoryBean getFilterFactory(DefaultWebSecurityManager mySecurityManager){
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(mySecurityManager);
//配置路径过滤器
Map<String,String> filterMap = new HashMap<>();
filterMap.put("/**","authc");
//静态资源不许登录都能访问
filterMap.put("/static/**","anon");
filterMap.put("/userLogin","anon");
filterMap.put("/userRegiste","anon");
filterMap.put("/registe","anon");
filterFactoryBean.setFilterChainDefinitionMap(filterMap);
filterFactoryBean.setLoginUrl("/login");
return filterFactoryBean;
}
}
这里注意,第一步配置的Realm对象被我注释了使因为我在public class MyShiroRealm extends AuthorizingRealm类中使用了@Configuration注解,springboot会将此类注册为Bean,所以不需要再去注册一个Realm的Bean。
3、书写Realm
import com.landa.gzdzll.pojo.User;
import com.landa.gzdzll.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.*;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
//身份认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
log.info(">>>执行身份认证");
//先获取当前用户
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
String username = userToken.getUsername();
//获取数据库的用户信息
User user = userService.getUserByUsername(username);
if (null == user)
return null;
//simpleAuthenticationInfo中传入数据库取出的密码和盐值,
//盐值类型需与用户注册时的密码盐值相同
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(username), "myShiroRealm");
return simpleAuthenticationInfo;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
首先看SimpleAuthenticationInfo类,上面提到shiro帮我们进行验证,可以保证身份信息安全不透明就是通过SimpleAuthenticationInfo类,将User对象和验证密码传入SimpleAuthenticationInfo类中,shiro会进行密码匹配后返回匹配信息。
4、登录控制器
import com.landa.gzdzll.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@Controller
@Slf4j
public class LoginController {
@RequestMapping("/userLogin")
@ResponseBody
public Object login(User user) {
log.info("==============用户登录============");
//获取交互用户subject
Subject subject = SecurityUtils.getSubject();
if (subject.getPrincipal() != null){
subject.logout();
}
Map<String, String> error = new HashMap<>();
if (!subject.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken(user.getLoginName(), user.getPassword());
try {
subject.login(token);
subject.getSession().setAttribute("loginUser", subject.getPrincipal());
return "login success";
} catch (UnknownAccountException uae) {
log.error("用户名不存在!");
error.put("errorMsg", "用户名不存在");
} catch (IncorrectCredentialsException ice) {
log.error("密码不正确!");
error.put("errorMsg", "密码不正确");
} catch (LockedAccountException lae) {
log.error("账号已锁定!");
error.put("errorMsg", "账号已锁定");
} catch (AuthenticationException ae) {
log.error("登录失败!");
error.put("errorMsg", "登录失败");
}
}
return error;
}
public Object logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "logout";
}
}
我写的登录控制器在开始的时候都去判断subject.getPrincipal()是否存在,如果不存在就不用登出,存在则先做登出操作后再进行身份验证,这是为了避免第一次登录完成后返回了登录页面,输入了错误密码或用户名后仍然能够登录成功的问题,因为第一次登陆后,shiro会记录身份信息,如果只是不小心返回了登录界面,此时未将shiro中的信息清空所以会出现此情况。
isAuthenticated方法是判断用户是否已验证,这个方法其实在写法上与上面 subject.logout();冲突,即已登出isAuthenticated一定是未验证状态,但此处仍然写上判断是为了防止出异常。
5、资源控制(过滤器)
资源控制一般写在config类中,需创建一个ShiroFilterFactoryBean,来监听资源,我的写在ShiroConfig,见上述代码,注意几个资源过滤器:
1、authc 此过滤器表示验证后可访问的资源,按需配置
2、anon 此过滤器表示无需验证也可访问的资源,例如/static/包下的资源需要使用anon,否则前端静态资源无法访问会导致页面显示异常。
3、ShiroFilterFactoryBean的setFilterChainDefinitionMap()方法是加载用户自定义资源控制器的方法,需传入一个map集合。
4、ShiroFilterFactoryBean的setLoginUrl();方法会将用户设置的login页面路径放行
5、其他控制器
6、其余配置
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*
* @return
*/
@Bean(name = "advisorAutoProxyCreator")
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean(name = "authorizationAttributeSourceAdvisor")
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManagerShiro());
return authorizationAttributeSourceAdvisor;
}
/**
* 前端使用shiro标签
* @return
*/
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
前两个是后端使用shiro注解,其实我觉得没必要使用,因为在过滤器工厂中配置了过滤器就可以,并且更方便管理,使用注解虽然代码量减少了,但总感觉很分散,不方便管理。最后一个是前端开启shiro标签的方法,在前端需要进行权限控制的时候很有必要配置,能过够对前端资源进行很好的配置。
注意:shiroDialect需要搭配下面这个jar包依赖
<!-- 页面使用shiro标签依赖 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
前端页面还需要添加shiro标签解析支持:
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">