首先shiro做安全框架,将JWT整合到shiro的认证和授权中进行使用,最终的目的就是用户在进行登录的时候,验证成功则返回一个token,后面该用户可以通过这个token是访问自己有权限的接口,启动缓存功能,只有第一次认证需要查询DB,后续访问都是访问缓存(分两种,单机版应用使用EhCache,分布式使用redis)
整合思路:
1.构建一个springBoot项目,引入核心依赖并展示核心代码目录
<!--引入ehcache依赖(单机版本缓存用) -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>
<!--redis整合springboot(分布式版本缓存用)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--引入shiro依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
<!--引入jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2、工具类JWTUtils、MyByteSource
public class JWTUtils {
private static String TOKEN = "token!Q@W3e4r";
/**
* 生成token
* @param map //传入payload
* @return 返回token
*/
public static String getToken(Map<String,String> map){
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v)->{
builder.withClaim(k,v);
});
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE,5);
builder.withExpiresAt(instance.getTime());
return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
}
/**
* 验证token
* @param token
* @return
*/
public static void verify(String token){
JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
}
/**
* 获取token中payload
* @param token
* @return
*/
public static DecodedJWT getToken(String token){
return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
}
}
//自定义salt实现 实现序列化接口
public class MyByteSource implements ByteSource,Serializable {
private byte[] bytes;
private String cachedHex;
private String cachedBase64;
public MyByteSource(){
}
public MyByteSource(byte[] bytes) {
this.bytes = bytes;
}
public MyByteSource(char[] chars) {
this.bytes = CodecSupport.toBytes(chars);
}
public MyByteSource(String string) {
this.bytes = CodecSupport.toBytes(string);
}
public MyByteSource(ByteSource source) {
this.bytes = source.getBytes();
}
public MyByteSource(File file) {
this.bytes = (new MyByteSource.BytesHelper()).getBytes(file);
}
public MyByteSource(InputStream stream) {
this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream);
}
public static boolean isCompatible(Object o) {
return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
}
public byte[] getBytes() {
return this.bytes;
}
public boolean isEmpty() {
return this.bytes == null || this.bytes.length == 0;
}
public String toHex() {
if (this.cachedHex == null) {
this.cachedHex = Hex.encodeToString(this.getBytes());
}
return this.cachedHex;
}
public String toBase64() {
if (this.cachedBase64 == null) {
this.cachedBase64 = Base64.encodeToString(this.getBytes());
}
return this.cachedBase64;
}
public String toString() {
return this.toBase64();
}
public int hashCode() {
return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof ByteSource) {
ByteSource bs = (ByteSource)o;
return Arrays.equals(this.getBytes(), bs.getBytes());
} else {
return false;
}
}
private static final class BytesHelper extends CodecSupport {
private BytesHelper() {
}
public byte[] getBytes(File file) {
return this.toBytes(file);
}
public byte[] getBytes(InputStream stream) {
return this.toBytes(stream);
}
}
}
3、自定义的realm,分别是CodeRealm、JwtRealm、PasswordRealm
/**
* 验证码方式验证realm
* 这里主要用到的是验证,不用关注授权,授权会在tokenRealm中进行授权
*/
@Slf4j
public class CodeRealm extends AuthorizingRealm {
@Autowired
private TUserDao tUserDao;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof CustomizedToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("CodeRealm权限认证开始,传递的token:{}",authenticationToken);
//找出数据库中的对象 给定用户输入的对象做出对比
CustomizedToken token = (CustomizedToken) authenticationToken;
log.info("CodeRealm转换的自定义token是:{}",token);
TUser user = tUserDao.findByUserName(token.getUsername());
if (Objects.nonNull(user)){
//假设发送的验证码默认是123,盐用123,散列次数1024
Md5Hash dasda = new Md5Hash("123", "123", 1024);
String code = dasda.toHex();
return new SimpleAuthenticationInfo(user.getUsername(),code,new MyByteSource("123"),this.getName());
}
return null;
}
}
/**
* jwt方式验证的realm
* 认证和授权都要进行处理
*/
@Slf4j
public class JwtRealm extends AuthorizingRealm {
@Autowired
private TUserDao tUserDao;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String token = (String) principalCollection.getPrimaryPrincipal();
DecodedJWT jwt = JWTUtils.getToken(token);
//拿到我们放进去的用户ID
String userName = jwt.getClaim("userName").asString();
if (!StringUtils.hasLength(userName)){
return null;
}
TUserService tUserService = (TUserService) ApplicationContextUtils.getBean("tUserService");
//查询数据库
TUser tUser = tUserService.findRolesByUserName(userName);
if (!CollectionUtils.isEmpty(tUser.getRoles())){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
tUser.getRoles().forEach(role ->{
//角色信息
simpleAuthorizationInfo.addRole(role.getName());
List<TPerms> perms = tUserService.findPermsByRoleId(role.getId());
if (!CollectionUtils.isEmpty(perms)){
perms.forEach(perm ->{
//资源信息
simpleAuthorizationInfo.addStringPermission(perm.getName());
});
}
});
return simpleAuthorizationInfo;
}
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
log.info("jwtRealm中关于身份验证的方法执行...传递的token是:{}",authenticationToken);
String token = (String) authenticationToken.getCredentials();
try {
//验证token
JWTUtils.verify(token);
}catch (SignatureVerificationException e){
log.info("签名不一致异常");
throw new AuthenticationException();
}catch (TokenExpiredException e){
log.info("令牌过期异常");
throw new AuthenticationException();
}catch (AlgorithmMismatchException e){
log.info("算法不匹配异常");
throw new AuthenticationException();
}catch (InvalidClaimException e){
log.info("失效的payload异常");
throw new AuthenticationException();
}
//不报错,说明token是有效的,那么直接进行认证逻辑
return new SimpleAuthenticationInfo(token,token,this.getName());
}
}
/**
* 账号密码验证realm
* 这里主要用到的是验证,不用关注授权,授权会在tokenRealm中进行授权
*/
@Slf4j
public class PasswordRealm extends AuthorizingRealm {
@Autowired
private TUserDao tUserDao;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof CustomizedToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("PasswordRealm权限认证开始,传递的token:{}",authenticationToken);
//找出数据库中的对象 给定用户输入的对象做出对比
CustomizedToken token = (CustomizedToken) authenticationToken;
log.info("PasswordRealm转换的自定义token是:{}",token);
TUser user = tUserDao.findByUserName(token.getUsername());
if (Objects.nonNull(user)){
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),new MyByteSource(user.getSalt()),this.getName());
}
return null;
}
}
4、重写的UsernamePasswordToken
@Data
public class CustomizedToken extends UsernamePasswordToken {
/**
* 登录类型
* Password是账号密码,Code是验证码
*/
public String loginType;
public CustomizedToken(final String username, final String password, final String loginType) {
super(username, password);
this.loginType = loginType;
}
}
5、JwtFilter最主要的目的是:当请求权限资源时,构造出主体subject执行login()进行认证和授权
/**
* JwtFilter最主要的目的是:当请求权限资源时,构造出主体subject执行login()进行认证和授权
*/
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {
/**
* 执行登录认证
* @param request ServletRequest
* @param response ServletResponse
* @param mappedValue mappedValue
* @return 是否成功
*/
@Override
//这个方法叫做 尝试进行登录的操作,如果token存在,那么进行提交登录,如果不存在说明可能是正在进行登录或者做其它的事情 直接放过即可
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
try {
executeLogin(request, response);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 执行登录
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response){
log.info("进入JwtFilter类中...");
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("token");
log.info("获取到的token是:{}",token);
// 判断token是否存在
if (!StringUtils.hasLength(token)) {
return false;
}
try{
JwtToken jwtToken = new JwtToken(token);
log.info("提交UserModularRealmAuthenticator决定由哪个realm执行操作...");
getSubject(request, response).login(jwtToken);
} catch (AuthenticationException e){
log.info("捕获到身份认证异常");
return false;
}
return true;
}
/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}
6、UserModularRealmAuthenticator,根据AuthenticationToken类型,判断我们使用哪个realm进行认证授权
/**
* 根据AuthenticationToken类型,判断我们使用哪个realm进行认证授权
*/
@Slf4j
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("UserModularRealmAuthenticator:method doAuthenticate() 执行 ");
// 判断getRealms()是否返回为空
assertRealmsConfigured();
//获取所有Realm
Collection<Realm> realms = getRealms();
// 放登录类型对应的所有Realm集合
Realm userRealm = null;
if (authenticationToken instanceof JwtToken){
JwtToken jwtToken = (JwtToken) authenticationToken;
//传进来的是Jwttoken,说明访问的是权限资源
for (Realm realm : realms){
String realmName = realm.getName();
if (realmName.contains("Jwt")){
userRealm = realm;
break;
}
}
return doSingleRealmAuthentication(userRealm, jwtToken);
}else {
//访问类型是公开类型
CustomizedToken customizedToken = (CustomizedToken) authenticationToken;
//获取登录类型
String loginType = customizedToken.getLoginType();
for (Realm realm : realms){
if (realm.getName().contains(loginType)){
userRealm = realm;
break;
}
}
return doSingleRealmAuthentication(userRealm, customizedToken);
}
}
}
7、自定义shiro缓存管理器RedisCacheManager
public class RedisCacheManager implements CacheManager {
//参数1:认证或者是授权缓存的统一名称
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
System.out.println(cacheName);
return new RedisCache<K,V>(cacheName);
}
}
8、自定义redis缓存的实现RedisCache
public class RedisCache<k,v> implements Cache<k,v> {
private String cacheName;
public RedisCache() {
}
public RedisCache(String cacheName) {
this.cacheName = cacheName;
}
@Override
public v get(k k) throws CacheException {
System.out.println("get key:"+k);
return (v) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
}
@Override
public v put(k k, v v) throws CacheException {
System.out.println("put key: "+k);
System.out.println("put value:"+v);
getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
return null;
}
@Override
public v remove(k k) throws CacheException {
System.out.println("=============remove=============");
return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
}
@Override
public void clear() throws CacheException {
System.out.println("=============clear==============");
getRedisTemplate().delete(this.cacheName);
}
@Override
public int size() {
return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
}
@Override
public Set<k> keys() {
return getRedisTemplate().opsForHash().keys(this.cacheName);
}
@Override
public Collection<v> values() {
return getRedisTemplate().opsForHash().values(this.cacheName);
}
private RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
9、上面基本把需要准备的都准备好了,剩下就是shiro的配置类
/**
* shiro配置类
*/
@Configuration
public class ShiroConfig {
/**
* 密码登录时指定匹配器
* @return HashedCredentialsMatcher
*/
@Bean("hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 设置哈希算法名称
matcher.setHashAlgorithmName("MD5");
// 设置哈希迭代次数
matcher.setHashIterations(1024);
// 设置存储凭证十六进制编码
matcher.setStoredCredentialsHexEncoded(true);
return matcher;
}
/**
* 如果需要密码匹配器则需要进行指定
* 只关注认证逻辑
* 密码登录Realm
* @return PasswordRealm
*/
@Bean
public PasswordRealm passwordRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
PasswordRealm passwordRealm = new PasswordRealm();
passwordRealm.setCredentialsMatcher(matcher);
return passwordRealm;
}
/**
* 如果需要密码匹配器则需要进行指定
* 只关注认证逻辑
* 密码登录Realm
* @return PasswordRealm
*/
@Bean
public CodeRealm codeRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher){
CodeRealm codeRealm=new CodeRealm();
codeRealm.setCredentialsMatcher(matcher);
return codeRealm;
}
/**
* jwtRealm
* token方式验证,认证和授权都要关注
* @return JwtRealm
*/
@Bean
public JwtRealm jwtRealm() {
JwtRealm jwtRealm = new JwtRealm();
//开启缓存管理器(EhCache适合单机版,数据缓存到服务应用内存中)
// jwtRealm.setCachingEnabled(true);
// jwtRealm.setAuthorizationCachingEnabled(true);
// jwtRealm.setAuthorizationCachingEnabled(true);
// jwtRealm.setCacheManager(new EhCacheManager());
//开启缓存管理(redis适合集群版,数据缓存到redis)
jwtRealm.setCacheManager(new RedisCacheManager());
jwtRealm.setCachingEnabled(true);//开启全局缓存
jwtRealm.setAuthenticationCachingEnabled(true);//认证认证缓存
jwtRealm.setAuthenticationCacheName("authenticationCache");
jwtRealm.setAuthorizationCachingEnabled(true);//开启授权缓存
jwtRealm.setAuthorizationCacheName("authorizationCache");
return jwtRealm;
}
/**
* 1.创建shiroFilter
* 主要负责资源权限的配置
* 配置认证和授权过滤器
* @param defaultWebSecurityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("shiroSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//配置系统受限资源
//配置系统公告资源
Map<String,String> filterMap = new HashMap<String,String>();
filterMap.put("/tUser/login.do","anon");//anon 设置为公共资源 放行资源放在下面
filterMap.put("/tUser/loginByCode.do","anon");//anon 设置为公共资源 放行资源放在下面
filterMap.put("/**","jwt");
//将权限的配置告诉给shiro
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//把我们自定义的jwt过滤器添加到shiro
LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>(1);
filtersMap.put("jwt",new JwtFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
return shiroFilterFactoryBean;
}
/**
* 2.创建安全管理器
* 把我们自定义的realm和重写的UserModularRealmAuthenticator添加到DefaultWebSecurityManager
*/
@Bean
public DefaultWebSecurityManager shiroSecurityManager(
@Qualifier("codeRealm")CodeRealm codeRealm,
@Qualifier("passwordRealm") PasswordRealm passwordRealm,
@Qualifier("jwtRealm") JwtRealm jwtRealm,
@Qualifier("userModularRealmAuthenticator") UserModularRealmAuthenticator userModularRealmAuthenticator) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm
securityManager.setAuthenticator(userModularRealmAuthenticator);
List<Realm> realms = new ArrayList<>();
// 添加多个realm
realms.add(passwordRealm);
realms.add(jwtRealm);
realms.add(codeRealm);
securityManager.setRealms(realms);
/*
* 关闭shiro自带的session,详情见文档
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
/**
* 重写的ModularRealmAuthenticator中的doAuthenticate方法
* 目的是根据传入的AuthenticationToken类型来判断使用我们哪个我自定义的realm进行验证授权
* @return
*/
@Bean
public UserModularRealmAuthenticator userModularRealmAuthenticator() {
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return modularRealmAuthenticator;
}
/**
* 以下Bean开启shiro权限注解
*
* @return DefaultAdvisorAutoProxyCreator
*/
@Bean
public static DefaultAdvisorAutoProxyCreator creator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
10、写好账号密码和验证码登录的两个接口
//用户密码登录
@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping(value = "/login.do",method = RequestMethod.POST)
public RetObj login(@Param("tUser") String tUser){
TUser tUserVo = JSONObject.parseObject(tUser, TUser.class);
RetObj ret = new RetObj();
if (Objects.isNull(tUserVo)){
ret.Fail("信息不能为空");
return ret;
}
try {
ret = tUserService.login(tUserVo.getUsername(), tUserVo.getPassword());
}catch (Exception e){
log.error("更新信息异常:"+ ExceptionUtil.getStackStr(e));
}
return ret;
}
//验证码登录
@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping(value = "/loginByCode.do",method = RequestMethod.POST)
public RetObj loginByCode(@Param("tUser") String tUser){
TUser tUserVo = JSONObject.parseObject(tUser, TUser.class);
RetObj ret = new RetObj();
if (Objects.isNull(tUserVo)){
ret.Fail("信息不能为空");
return ret;
}
try {
ret = tUserService.loginByCode(tUserVo.getUsername(), tUserVo.getPassword());
}catch (Exception e){
log.error("更新信息异常:"+ ExceptionUtil.getStackStr(e));
}
return ret;
}
11、对应的业务层代码
@Override
public RetObj login(String username, String password) {
RetObj retObj = new RetObj();
Subject subject = SecurityUtils.getSubject();
CustomizedToken customizedToken = new CustomizedToken(username, password, "Password");
try {
subject.login(customizedToken);//用户登录
System.out.println("登录成功~~");
} catch (UnknownAccountException e) {
retObj.Fail("用户名错误");
return retObj;
}catch (IncorrectCredentialsException e){
retObj.Fail("密码错误");
return retObj;
}
TUser user = new TUser();
user.setUsername(username);
List<TUser> tUsers = selectList(user, null, null);
if (!CollectionUtils.isEmpty(tUsers)){
Map<String,String> map = new HashMap<>();
map.put("userId",String.valueOf(tUsers.get(0).getId()));
map.put("userName",String.valueOf(tUsers.get(0).getUsername()));
String token = JWTUtils.getToken(map);
retObj.setMsg(token);
return retObj;
}
return retObj;
}
@Override
public RetObj loginByCode(String username, String code) {
RetObj retObj = new RetObj();
Subject subject = SecurityUtils.getSubject();
CustomizedToken customizedToken = new CustomizedToken(username, code, "Code");
try {
subject.login(customizedToken);//用户登录
System.out.println("登录成功~~");
} catch (UnknownAccountException e) {
retObj.Fail("用户名错误");
return retObj;
}catch (IncorrectCredentialsException e){
retObj.Fail("密码错误");
return retObj;
}
TUser user = new TUser();
user.setUsername(username);
List<TUser> tUsers = selectList(user, null, null);
if (!CollectionUtils.isEmpty(tUsers)){
Map<String,String> map = new HashMap<>();
map.put("userId",String.valueOf(tUsers.get(0).getId()));
map.put("userName",String.valueOf(tUsers.get(0).getUsername()));
String token = JWTUtils.getToken(map);
retObj.setMsg(token);
return retObj;
}
return retObj;
}
12、分别进行登录测试,都能拿到正确的token
13、加一个全局异常捕获,主要是捕获shiro验证的报错从而返回提示信息
@ControllerAdvice
public class NoPermissionException {
@ResponseBody
@ExceptionHandler(UnauthorizedException.class)
public RetObj handleShiroException(Exception ex) {
RetObj retObj = new RetObj();
retObj.Fail("无权限");
return retObj;
}
@ResponseBody
@ExceptionHandler(AuthorizationException.class)
public RetObj authorizationException(Exception ex) {
RetObj retObj = new RetObj();
retObj.Fail("身份验证失败");
return retObj;
}
@ResponseBody
@ExceptionHandler(UnauthenticatedException.class)
public RetObj unauthenticatedException(Exception ex) {
RetObj retObj = new RetObj();
retObj.Fail("请先登录");
return retObj;
}
}
14、对有权限的接口进行请求
14.1不带token请求
14.2请求没有授权的资源
14.3请求授权的资源
总结
当回头看代码时,整个思路变得特别清晰,我们再回顾一次整个项目,当前端请求访问授权资源时,会被shiro拦截,代码将执行到JwtFilter(重写了BasicHttpAuthenticationFilter中的isAccessAllowed和executeLogin方法)类中的isAccessAllowed方法,然后执行其中executeLogin方法,初始化JwtToken(重新实现了AuthenticationToken)然后构造出主体subject执行login()进行认证和授权,这时候代码将执行到UserModularRealmAuthenticator(重写了ModularRealmAuthenticator的doAuthenticate方法)类中的doAuthenticate方法,然后根据AuthenticationToken类型,使用指定的realm进行认证授权,执行JwtRealm(重新了AuthorizingRealm的doGetAuthenticationInfo和doGetAuthorizationInfo方法)中的认证方法doGetAuthenticationInfo和授权方法doGetAuthorizationInfo,整个请求的流程就结束了,启动了redis缓存,只有第一次会访问数据库,后面都是访问redis缓存。