springboot+shiro整合
springboot+shiro
最近在看shiro和jwt,遇到了好多坑,现在总结一下
不了解shiro的话,可以先看下这个:shiro基础教程
以下内容参考(写的很简洁明了):教你 Shiro 整合 SpringBoot,避开各种坑
导包
<dependency
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
springboot整合shiro
shiro配置类
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
// RestShiroFilterFactoryBean shiroFilterFactoryBean = new RestShiroFilterFactoryBean();
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean ()
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
shiroFilterFactoryBean.setLoginUrl("/notLogin");
// 设置无权限时跳转的 url;
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
// 设置拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/logout","anon");
filterChainDefinitionMap.put("/login","anon");
filterChainDefinitionMap.put("/user/**","roles[user]");
//其余接口一律拦截
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("Shiro拦截器工厂类注入成功");
return shiroFilterFactoryBean;
}
/**
* 注入 securityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm();
//securityManager.setRealms(Arrays.asList(customRealm(hashedCredentialsMatcher()),jwtRealm()));
return securityManager;
}
/**
* 自定义身份认证 realm;
* <p>
* 必须写这个类,并加上 @Bean 注解,目的是注入 CustomRealm,
* 否则会影响 CustomRealm类 中其他类的依赖注入
*/
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm=new CustomRealm();
return customRealm;
}
/**
* 禁用session, 不保存用户登录状态。保证每次请求都重新认证。
* 需要注意的是,如果用户代码里调用Subject.getSession()还是可以用session,如果要完全禁用,要配合下面的noSessionCreation的Filter来实现
*/
@Bean
protected SessionStorageEvaluator sessionStorageEvaluator(){
DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
sessionStorageEvaluator.setSessionStorageEnabled(false);
return sessionStorageEvaluator;
}
}
注意:里面的 SecurityManager 类导入的应该是 import org.apache.shiro.mgt.SecurityManager; 但是,如果你是复制代码过来的话,会默认导入 java.lang.SecurityManager 这里也稍稍有点坑,其他的类的话,也是都属于 shiro 包里面的类
shirFilter 方法中主要是设置了一些重要的跳转 url,比如未登陆时,无权限时的跳转;以及设置了各类 url 的权限拦截,比如 /user 开始的 url 需要 user 权限,/admin 开始的 url 需要 admin 权限等
(这里的跳转会涉及到跨域的问题,因为rediret重定向在跨域中会有问题)
注意:anon, authc, authcBasic, user 是第一组认证过滤器,perms, port, rest, roles, ssl 是第二组授权过滤器,要通过授权过滤器,就先要完成登陆认证操作(即先要完成认证才能前去寻找授权) 才能走第二组授权器(例如访问需要 roles 权限的 url,如果还没有登陆的话,会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的 url )
自定义realm类
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
/**
* 当token类型是UsernamePasswordToken时才调用这个realm(在之后整合jwt的时候还有一个jwttoken,所以这里要加这个)
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
/**
* 身份认证
* Shiro中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。
* @param authenticationToken 用户身份信息 token
* @return 返回封装了用户信息的 AuthenticationInfo 实例
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 从数据库获取对应用户名密码的用户
User user = userMapper.selectbyUsername(token.getUsername());
String password="";
if(user!=null)
{password=user.getPassword();}
return new SimpleAuthenticationInfo(token.getPrincipal(),
password,
getName());
}
/**
* 权限认证
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String) SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获得该用户角色
String role=userMapper.selectbyUsername(username).getRole();
//String role = userMapper.getRole(username);
Set<String> set = new HashSet<>();
//需要将 role 封装到 Set 作为 info.setRoles() 的参数
set.add(role);
//设置该用户拥有的角色
info.setRoles(set);
return info;
}
}
重写的两个方法分别是实现身份认证以及权限认证,shiro 中有个作登陆操作的 Subject.login() 方法,当我们把封装了用户名,密码的 token 作为参数传入,便会跑进这两个方法里面(不一定两个方法都会进入)
其中 doGetAuthorizationInfo 方法只有在需要权限认证时才会进去,比如前面配置类中配置了 filterChainDefinitionMap.put("/user/**", "roles[user]"); 的管理员角色,这时进入 /user 时就会进入 doGetAuthorizationInfo 方法来检查权限;而 doGetAuthenticationInfo 方法则是需要身份认证时(比如前面的 Subject.login() 方法)才会进入
再说下 UsernamePasswordToken 类,我们可以从该对象拿到登陆时的用户名和密码(登陆时会使用 new UsernamePasswordToken(username, password);)
loginController
@RequestMapping(value = "/login")
public boolean login(@RequestBody User user ) {
String username=user.getUsername();
String password=user.getPassword();
// 从SecurityUtils里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 执行认证登陆
//在登录时会调用CustomRealm中的doGetAuthenticationInfo方法,返回一个SimpleAuthenticationInfo
//这个是在内部根据usernamepasswordtoken判断用户名密码是否正确,如果正确的话不会抛出异常,否则抛出
//异常,在loginController中将异常捕获,并返回false
try {
subject.login(token);
return true;
}catch (Exception e)
{
return false;
}
}