前言
之前在学习完Jwt之后得相关之后,开始想着Jwt与shiro整合。进入了编码得环节才发现其实并没有那么简单。好了现在就开始记录一下吧。
当然这个整合过程会有一些bug,如果此时你看到了这篇文章,自己也想去整合一下shrio和jwt,那样不妨留下一个评论,多多讨论一下。这样也能大家都能学到一些知识。
技术栈
Shiro
Jwt
由于是一个Springboot项目,还需要了解Springboot相关得知识。
编码
1、导入shiro和jwt依赖
<!-- 配置 Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!--日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>test</scope>
</dependency>
<!-- 配置 JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
2、配置shiro
在ShiroConfig中,我们需要配置一下几点
1、禁用Session
2、增加自定义JwtFilter过滤器,用来拦截请求,使全部的请求或这某些特的请求由过滤器先行过滤
3、自定义JwtRealm,用来数据验证。
package com.ll.config;
import com.ll.shiro.JwtFilter;
import com.ll.shiro.JwtRealm;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//向spring容器中注入 `JwtRealm` bean
@Bean
public JwtRealm jwtRealm()
{
return new JwtRealm();
}
//禁用session, 不保存用户登录状态。保证每次请求都重新认证
@Bean
protected SessionStorageEvaluator sessionStorageEvaluator()
{
DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
sessionStorageEvaluator.setSessionStorageEnabled(false);
return sessionStorageEvaluator;
}
` //配置安全管理器 SecurityManage
//并关联上JwtRealm
@Bean
public SecurityManager securityManager(JwtRealm jwtRealm)
{
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//配置Realm
securityManager.setRealm(jwtRealm);
//关闭shiro自带得session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator());
return securityManager;
}
//注意这里是是十分重要的一点,如果没有这个bean,会出现一个关于`securityManager`无效配置的一个错误
/**
* 不向 Spring容器中注册 JwtFilter Bean,防止 Spring 将 JwtFilter 注册为全局过滤器
* 全局过滤器会对所有请求进行拦截,而本例中只需要拦截除 /login 和 /logout 外的请求
* 另一种简单做法是:直接去掉 jwtFilter()上的 @Bean 注解
*/
@Bean
public FilterRegistrationBean<Filter> registration(JwtFilter filter) {
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<Filter>(filter);
registration.setEnabled(false);
return registration;
}
//向spring容器中注入 `JwtFilter`
@Bean
public JwtFilter jwtFilter()
{
return new JwtFilter();
}
// 设置拦截策略链,在这里我们的拦截策略是,所有的请求都由 `JwtFilter` 进行拦截,包括login,
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition()
{
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
//filterMap.put("/login", "anon");
filterMap.put("/**","JwtFilter"); 需登录才能访问
chainDefinition.addPathDefinitions(filterMap);
return chainDefinition;
}
/**
* 配置访问资源需要的权限
*/
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
ShiroFilterChainDefinition shiroFilterChainDefinition)
{
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
filterFactoryBean.setSecurityManager(securityManager);
//自定义jwt专用过滤器
Map<String, Filter> filters = new LinkedHashMap<>();
filters.put("JwtFilter",jwtFilter());
filterFactoryBean.setFilters(filters);
Map<String, String> filterChainMap = shiroFilterChainDefinition.getFilterChainMap();
filterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return filterFactoryBean;
}
}
3、JwtFilter
package com.ll.shiro;
import com.ll.utils.JwtUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
这里我们自定义的拦截器需要去继承一个 ` AuthenticatingFilter`,并根据我们的需要来重写方法。
*/
public class JwtFilter extends AuthenticatingFilter {
//从hander中提出Jwt token
//由于shiro默认的token是`UsernameAndPassword`的形式,所以我们需要改变这样的形式。
//这里我们需要自定义一个自己的token。
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception
{
System.out.println("执行了createToken");
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String authorization = httpServletRequest.getHeader("Authorization");
if(StringUtils.isEmpty(authorization))
{
return null;
}
JwtToken token = new JwtToken(authorization);
return token;
}
/**
每次进行请求时,都会首先 `onAccessDenied`进行捕获,进行判断,如果时初次登录,集request中没有携带token,将会直接被放行,直接进行登录的操作,如果第二次进行请求,那么此时就会对是否由token吗以及token是否正确进行判断。如果不正确,将直接会拦截请求
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
System.out.println("onAccessDenied执行了");
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String authorization = httpServletRequest.getHeader("Authorization");
if(StringUtils.isEmpty(authorization))
{
return true; //放行
}
else
{
if(!JwtUtils.verify(authorization,"feng",JwtUtils.SECRET))
{
System.out.println("token以失效,请重新登录");
return false;
//throw new ExpiredCredentialsException("token以失效,请重新登录");
}
}
return executeLogin(servletRequest,servletResponse);
}
}
4、JwtToken
package com.ll.shiro;
import org.apache.shiro.authc.AuthenticationToken;
public class JwtToken implements AuthenticationToken {
private String token;
public JwtToken(String jwt)
{
this.token = jwt;
}
@Override
public Object getPrincipal() {
return token;
}
//凭证信息
@Override
public Object getCredentials() {
return token;
}
}
5、JwtReanlm
package com.ll.shiro;
import com.ll.utils.JwtUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class JwtRealm extends AuthorizingRealm {
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
当`onAccessDenied`进行判断之后,如果token都正确之后,那么,将会执行`executeLogin`方法,并在其内部调用了`createToken`方法,最终将登陆的数据交给JwtReanlm进行认证。在这里我们为了快速的进行的验证,模拟的是一个假的数据,在真实的开发中,此处就可以链接数据库进行数据库的查询和认证了。
*/
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
System.out.println("开始执行了认证");
JwtToken jwtToken = (JwtToken) token;
String username = JwtUtils.getClaimFiled((String)jwtToken.getPrincipal(),"username");
System.out.println(username);
if(!username.equals("feng"))
{
System.out.println("账户不存在");
//throw new UnknownAccountException("账户不存在");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, jwtToken.getCredentials(), getName());
return info;
}
}
最后我们就可以去开发我们的controller层了。具体的内容在这里就贴出来了,当然这个整合过程会有一些bug,如果此时你看到了这篇文章,自己也想去整合一下shrio和jwt,那样不妨留下一个评论,多多讨论一下。这样也能大家都能学到一些知识。
好了,下次见