springboot使用shiro完成token认证

现在都是token无状态的认证,今天记录一下如何使用shiro完成token登录认证,话不多说,直接上代码
依赖

<!-- shiro -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.1</version>
		</dependency>

shiro的主配置类


import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
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.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {

	@Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        bean.setLoginUrl("/login");
        // 添加自定义过滤器
        Map<String, Filter> filterMap = new HashMap<>(16);
        filterMap.put("tokenFilter", new TokenFilter());
        bean.setFilters(filterMap);
        /**
         * 自定义拦截规则
         */
        Map<String, String> filterRuleMap = new LinkedHashMap<>();
        filterRuleMap.put("/login","anon");
        filterRuleMap.put("/noPer","anon");//不需要认证,可以直接访问的
        // 其余请求都要经过BearerTokenFilter自定义拦截器
        filterRuleMap.put("/**", "tokenFilter");
        bean.setFilterChainDefinitionMap(filterRuleMap);
        return bean;
    }
	
    @Bean(name="securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
    	/**
    	 * 托管userrealm
    	 */
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        userRealm.setAuthenticationTokenClass(JwtToken.class);//如果不进行设置就默认UsernamePasswordToken
        securityManager.setRealm(userRealm);
        /**
         * 禁用session
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }
	
    /**
     * 自定义realm
     * @return
     */
	@Bean(name = "userRealm")
	public UserRealm userRealm(){
		return new UserRealm();
	}
	

	//开启shiro aop注解支持  作用在方法上
	@Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
	
	/**
	 * 让注解权限生效(如果注解权限不生效)
	 * @return
	 */
	@Bean
	public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
	    return new DefaultAdvisorAutoProxyCreator();
	}
}

自定义realm实现类

import java.util.Arrays;
import java.util.List;

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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import com.spring.framework.entity.LoginUser;
import com.spring.framework.info.DataContextSupport;
import com.spring.framework.utils.RedisUtil;

public class UserRealm extends AuthorizingRealm{
	

	@Autowired
	RedisUtil redisUtil;
	/**
	 * 等同于配置类的指定token类型
	 */
	@Override
	public boolean supports(AuthenticationToken token) {
		return token instanceof JwtToken;
	}
	
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		LoginUser user = (LoginUser)principals.getPrimaryPrincipal();//获取到用户
		String[] perms = {"sys:user:del","sys:user:add","sys:user:update"};
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		if(user.getUserName().equals("ljw")){//该用户拥有所有权限
		    info.addStringPermission("*:*:*");
		    info.addRole("*");
		}else {
			List<String> asList = Arrays.asList(perms);
			asList.forEach(str->{
		    	if(!StringUtils.isEmpty(str)){
		    		info.addStringPermission(str);
		    	}
		    });
		}
		return info;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
		JwtToken jwtToken = (JwtToken)authenticationToken;
		String token = (String) jwtToken.getPrincipal();
		//token是否存在
		if (StringUtils.isEmpty(token)) {
            throw new AuthenticationException(" 非法请求,请先登录! ");
        }
		LoginUser loginUser = (LoginUser)redisUtil.get(token);
		if(loginUser==null) {
			  throw new AuthenticationException(" 登录过期,请重新登录 ");
		}
		long currentTimeMillis = System.currentTimeMillis();
		long e= 30 * 60 * 1000L;//三十分钟
		if(currentTimeMillis>=loginUser.getExpireTime()) {
			  throw new AuthenticationException(" 登录过期,请重新登录 ");
		}
		long refresTime=  loginUser.getExpireTime()-System.currentTimeMillis();
		if(refresTime<=5 * 60 * 1000L) {//有效期小于等于五分钟,刷新token
			long expireTime = currentTimeMillis+e;
			loginUser.setExpireTime(expireTime);
		}
		redisUtil.set(token, loginUser, 60*30);//redis token再次刷新值
		DataContextSupport.setDataPermissions(loginUser);
		return new SimpleAuthenticationInfo(loginUser,jwtToken.getPrincipal(),getName());
	}

}

自定义token

import org.apache.shiro.authc.AuthenticationToken;

public class JwtToken implements AuthenticationToken {

	private static final long serialVersionUID = 1L;

	private String token;

	public JwtToken(String token) {
		this.token = token;
	}

	@Override
	public Object getPrincipal() {
		return token;
	}

	@Override
	public Object getCredentials() {
		return token;
	}
}

token过滤器

import java.io.IOException;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import mlt.boot.blog.result.Result;

public class TokenFilter extends BasicHttpAuthenticationFilter{
 	//源码注释
    // Check whether the current request's method requires authentication.
    // If no methods have been configured, then all of them require auth,
    // otherwise only the declared ones need authentication.
	 /**
     * 作用:个人感觉判断是否需要认证,需要认证的话就进入下面的逻辑
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
     return super.isAccessAllowed(request, response, mappedValue) ||(!isLoginRequest(request, response) && isPermissive(mappedValue));
    }

    /**
     * 认证未通过执行该方法
     * @param request
     * @param response
     * @return
     * @throws IOException 
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException{
        //完成token登入
        //1.检查请求头中是否含有token
        HttpServletRequest httpServletRequest= (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse)response;
        String token = httpServletRequest.getHeader("Authorization");
        JwtToken jwtToken = new JwtToken(token);
        try {
            SecurityUtils.getSubject().login(jwtToken);
            /**
             * 这里抛异常处理
             * Result(code=400, msg=Realm [mlt.boot.blog.config.UserRealm@2e2bff56] does not support authentication 
             * token [mlt.boot.blog.config.JwtToken@1db51365].  
             * Please ensure that the appropriate Realm implementation is configured correctly or that the realm accepts 
             * AuthenticationTokens of this type., data=null)
             */
        } catch (AuthenticationException e) {
        	//这里在全局异常里面捕获不到,所以把信息封装成json返回
        	Throwable throwable = e.getCause() == null ? e : e.getCause();
            Result r = Result.fail(throwable.getMessage());
            String json = JSONObject.toJSONString(r);
            httpServletResponse.setHeader("content-type", "text/html;charset=UTF-8");//防止乱码
            httpServletResponse.getWriter().print(json);
            return false;
        }
        return true;
    }


	
	 /**
	  * 方法执行前
     * 对跨域提供支持
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @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);
    }

}

我的几个请求测试

@PostMapping("/login")
	public Result login(@RequestBody LoginUser loginUser) {
		String usrename= loginUser.getUserName();
		String password= loginUser.getPassWord();
		if(StringUtils.isEmpty(usrename)||StringUtils.isEmpty(password)) {
			Result.fail("登录参数不能为空");
		}
		if(usrename.equals("ljw")&&password.equals("ljw")) {
			String uuid = UUID.randomUUID().toString().replace("-", "");
			loginUser.setUuid(uuid);
			long currentTimeMillis = System.currentTimeMillis();
			long e= 30 * 60 * 1000L;//三十分钟
			long expireTime = currentTimeMillis+e;
			loginUser.setExpireTime(expireTime);
			redisUtil.set(uuid, loginUser, 60*30);//redis三十分钟有效期
			return Result.ok(uuid,"登录成功");
		}else if(usrename.equals("test")&&password.equals("test")){
			String uuid = UUID.randomUUID().toString().replace("-", "");
			loginUser.setUuid(uuid);
			long currentTimeMillis = System.currentTimeMillis();
			long e= 30 * 60 * 1000L;//三十分钟
			long expireTime = currentTimeMillis+e;
			loginUser.setExpireTime(expireTime);
			redisUtil.set(uuid, loginUser, 60*30);//redis三十分钟有效期
			return Result.ok(uuid,"登录成功");
		}else {
			return Result.fail("用户可能不存在");
		}
	}
	
	/**
	 * 获取当前登录用户信息
	 * @return
	 */
	@GetMapping("/userInfo")
	public Result getUserInfo() {
		LoginUser loginUser = DataContextSupport.getDataPermissions();
		return Result.ok(loginUser);
	}
	
	@GetMapping("/test1")
	@RequiresPermissions("sys:user:del")
	public Result test1() {
		LoginUser loginUser = DataContextSupport.getDataPermissions();
		return Result.ok(loginUser);
	}
	
	@GetMapping("/test2")
	@RequiresPermissions("sys:role:delete")
	public Result test2() {
		LoginUser loginUser = DataContextSupport.getDataPermissions();
		return Result.ok(loginUser);
	}

登录实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 用户登录
 * @author ljw
 *
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements Serializable{

	private static final long serialVersionUID = 1L;

	private String userName;
	
	private String passWord;
	
	private String uuid;
	
    private Long expireTime;//过期时间 
    
    //private SysUser sysUser;//可存放数据库用户信息
}

DataContextSupport

import org.springframework.stereotype.Component;
import com.spring.framework.entity.LoginUser;
import java.util.Optional;

/**
 * @author ljw
 * 2021年6月19日17:04:083
 */
@Component
public class DataContextSupport {

    public static final ThreadLocal<LoginUser> dataContext = new ThreadLocal<>();

    private static final LoginUser DATA_PERMISSIONS = new LoginUser();

    /**
     * 设置当前用户信息
     */
    public static void setDataPermissions(LoginUser loginUser) {
        dataContext.set(loginUser);
    }

    /**
     * 获取当前用数据信息
     */
    public static LoginUser getDataPermissions() {
        return Optional.ofNullable(dataContext.get()).orElse(DATA_PERMISSIONS);
    }

    /**
     * 移除当前用户数据信息
     */
    public static void close() {
        dataContext.remove();
    }
}

拦截器,处理DataContextInterceptor的,防止内存溢出,最后记得配置。让拦截器生效

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
 * 释放资源
 * @author ljw
 *
 */
public class DataContextInterceptor implements  HandlerInterceptor  {
    /**
     * 拦截前
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
   
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }
    /**
     * 拦截后
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    	DataContextSupport.close();
    }
}

登录测试
在这里插入图片描述

没有登录去请求
在这里插入图片描述
访问有权限的
在这里插入图片描述
访问无权限的
在这里插入图片描述
最后贴上全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {

    private  final  static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler. class);
    
    /**
     * 无权限操作异常
     * @param req
     * @return
     */
    @ExceptionHandler(AuthorizationException.class)
	public Result NoPerm(HttpServletRequest req){
		logger.info("请求[ "+req.getRequestURI()+" ]验证未通过:{}", "无权限操作");
		return Result.fail("无权限操作",403);
	}

    @ExceptionHandler(value= RuntimeException.class)
    public Result runTimeException(RuntimeException e, HttpServletRequest req){
        logger.error("错误请求地址URI [{}]", req.getRequestURI());
        return Result.fail(e.getMessage());
    }
}
  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot和Shiro是两个独立的开源项目,可以结合使用来实现身份认证和授权功能。在Spring Boot中集成Shiro,可以使用Shiro提供的Token来进行用户身份认证。 在使用Shiro进行身份认证时,可以使用不同类型的Token,比如UsernamePasswordToken、JWTToken等。这些Token都实现了Shiro的AuthenticationToken接口,用于传递用户的身份信息。 下面是一个使用Shiro Token进行身份认证的简单示例: 1. 首先,需要在Spring Boot的依赖管理中添加Shiro和相应的Token库的依赖,比如shiro-springshiro-jwt等。 2. 创建一个自定义的Token类,继承Shiro的AuthenticationToken接口,并实现其中的方法。该Token类可以封装用户的身份信息,比如用户名和密码等。 3. 在Shiro的配置类中,配置认证器(Authenticator)和Realm等相关信息。认证器负责验证用户的身份信息,并将其与数据库或其他存储中的用户信息进行比对。 4. 在需要进行身份认证的地方,可以通过SecurityUtils获取当前Subject对象,并调用其login方法进行身份认证。在login方法中,将自定义的Token对象传入即可。 5. 根据需要,可以使用Shiro的注解来进行授权操作,比如@RequiresPermissions、@RequiresRoles等。 这只是一个简单的示例,具体的使用方式还需要根据具体的需求和场景来进行调整。你可以参考Shiro的官方文档和Spring Boot的相关文档,深入了解和学习如何使用Spring Boot和Shiro来实现身份认证和授权功能。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值