Shiro+JWT
关于两大认证鉴权框架,springsecurity,shiro,确实瞎看些文章,眼花缭乱,用了一周的时间差不多了解了,学习过程中可谓晕头转向的,后面在谛听上看到shiro文章很好,又得到行内大佬指点一下(鉴权信息来源于认证),算是弄懂了shiro,然后也对springsecurity有了基本了解
继承BasicHttpAuthenticationFilter实现自定义JWTFilter
package com.fintech.abchina.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
public class MyJWTFilter extends BasicHttpAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
System.err.println("isAccessAllowed");
HttpServletRequest request1 = (HttpServletRequest) request;
String requestURI = request1.getRequestURI();
System.err.println("requestURI = " + requestURI);
if (requestURI.contains("login") || requestURI.contains("/js")) {
//这里为了测试使用,写的不是很标准,作用就是释放登录和静态资源请求
return true;
} else {
if (isLoginAttempt(request, response)) {
try {
this.executeLogin(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return false;
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
System.err.println("executeLogin");
HttpServletRequest request1 = (HttpServletRequest) request;
String token1 = request1.getHeader("token");;
System.err.println("token1 = " + token1);
JwtToken jwtToken = new JwtToken(token1);
System.err.println("jwtToken = " + jwtToken);
try {
this.getSubject(request, response).login(jwtToken);
//这里调用的其实就是ShiroRealm中的doAuthentication方法
} catch (AuthenticationException e) {
e.printStackTrace();
}
return true;
}
//判断是否携带token,携带token则进行后续认证
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
System.err.println("isLoginAttempt");
//顾名思义,这方法名不是代表登录吗,而现在后续认证,应该非首次登录
HttpServletRequest request1 = (HttpServletRequest) request;
String token1 = request1.getHeader("token");
return token1 != null;
}
}
继承AuthorizingRealm实现自己的Realm
package com.fintech.abchina.config;
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 java.util.HashSet;
import java.util.Set;
/**
* @Author: GQS
* @Description:
* @Date: 2021/8/12 17:18
*/
public class ShiroRealm extends AuthorizingRealm {
//将shiro默认token修改为自定义的token
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
//授权(即用户访问有要求权限接口时获取要求权限,再在这里获取该角色具有哪些权限,由shiro进行比较 )
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//这个principalCollection是shiro根据认证方法返回的对象第一个参数提供的。
System.err.println("doGetAuthorizationInfo");
System.out.println("principalCollection = " + principalCollection);
System.out.println("看一下PrincipalCollection 使用的哪个实现类 " + principalCollection.getClass());
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
Set<String> roleSet = new HashSet<>();
//实际这个是要根据principalCollection来查的
roleSet.add("admin");
simpleAuthorizationInfo.setRoles(roleSet);
return simpleAuthorizationInfo;
//返回权限后,就由shiro来进行用户该访问接口的权限判断啦。
//应该是每次都要进行判断的,因为每次请求都要走到这里,多写一个ajax按钮来判断一下
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.err.println("doGetAuthenticationInfo");
String token = "";
try {
token = (String) authenticationToken.getPrincipal();
} catch (Exception e) {
System.out.println("e.getMessage() = " + e.getMessage());
}
System.out.println("对token进行判断过期,是否为空等");
//根据传进来token
String username= JwtUtil.getUserNameByToken(token);
System.out.println("根据username查询数据库User信息,再使用当初返回该token相同方式生成token1");
String token_right= username+"";
System.out.println("这里为了测试,直接省略具体生成方法");
//第一个参数可以返回用户名,认证通过后用于鉴权。
//根据前台提供的token信息按照规则(利用真是比如密码)生成一个新的token_right,由shiro进行两者的比较
//即authenticationToken.getCredentials和token1进行比较。可以打断点进去查看的
return new SimpleAuthenticationInfo("principal", token_right, "my_shiro_realm");
}
}
实现AuthenticationToken接口,实现自定义的token
需要在自定义Realm这里叫ShiroRealm进行配置
package com.fintech.abchina.config;
import lombok.Data;
import org.apache.shiro.authc.AuthenticationToken;
import java.io.Serializable;
@Data
public class JwtToken implements AuthenticationToken {
private String token;
private String expireAt;
public JwtToken(String token) {
this.token = token;
this.expireAt="time";
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
配置类配置,将类进行绑定
package com.fintech.abchina.config;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextListener;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
@Configuration
public class ShiroConfig {
//这里测一下是否一定要将MyJwtFilter加入到shiro过滤器链中 -是的,不加不执行
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//在shiro过滤器链上加入MyJwtFilter,不加入不会执行。
LinkedHashMap<String, Filter> filters = new LinkedHashMap<>();
filters.put("jwt", new MyJWTFilter());
shiroFilterFactoryBean.setFilters(filters);
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//过滤器链定义,由上到下执行
filterChainDefinitionMap.put("/**", "jwt");
//这个和前面对应
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(getShiroRealm());
return securityManager;
}
//将自定义Realm加入到Spring环境中
@Bean
public ShiroRealm getShiroRealm() {
return new ShiroRealm();
}
//用于鉴权时候使用的
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
//配置鉴权生效的(具体不清楚)
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
//暂时不清楚
@Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
}
异常处理
有个很重要思想就是shiro内部异常由shiro拦截,我们只能捕获最上层异常
package com.fintech.abchina.config;
import org.apache.shiro.authc.CredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class ControllerAdvice {
@ExceptionHandler(ShiroException.class)
Map handleShiroException(ShiroException e) {
System.err.println("e.getClass() = " + e.getClass());
HashMap<String, String> resultMap = new HashMap<>();
if (e instanceof UnauthenticatedException) {
System.out.println("认证异常");
resultMap.put("msg", "认证异常");
} else if (e instanceof UnauthorizedException) {
System.out.println("权限异常");
resultMap.put("msg", "权限异常");
} else {
System.out.println("其他异常");
resultMap.put("msg", "其他异常");
}
return resultMap;
}
}
很重要的思想
1.通过了自定义jwtfilter的isAccessAllowed返回true即登录和静态资源无需后面认证
2.Realm是由自定义的jwtfilter调用
3.授权和认证都有shiro自己完成,我们在认证和授权里需要做的就是返回信息
4.授权的用户信息通过认证返回的类获得,即先认证后授权