shiro+jwt登陆验证

学习目标:

例如:

  • 一小时实现shiro+jwt实现登录认证

学习内容:

例如:

  1. 了解shiro框架
  2. 使用jwt生成身份令牌

项目目录:
在这里插入图片描述

直接上代码:

这个过滤器是我们的重点,这里我们继承的是Shiro内置的BasicHttpAuthenticationFilter ,一个可以内置了可以自动登录方法的的过滤器,有些同学继承AuthenticatingFilter也是可以的,根据个人情况。

@Component
@Slf4j
public class JWTFilter extends BasicHttpAuthenticationFilter {


    /**
     * 认证之前执行该方法
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = SecurityUtils.getSubject();
        return  null != subject && subject.isAuthenticated();
    }

    /**
     * 认证未通过执行该方法
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response){
        //完成token登入
        //1.检查请求头中是否含有token
        HttpServletRequest httpServletRequest= (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Authorization");
        //2. 如果客户端没有携带token,拦下请求
        if(null==token||"".equals(token)){
            responseTokenError(response, RCode.INVALID_TOKEN);
            return false;
        }
        //3. 如果有,对进行进行token验证
        JWTToken jwtToken = new JWTToken(token);
        try {
            SecurityUtils.getSubject().login(jwtToken);
        } catch (AuthenticationException e) {
            log.error(e.getMessage());
            responseTokenError(response,RCode.AUTH_ERROR);
            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);
    }
    /**
     * 无需转发,直接返回Response信息 Token认证错误
     */
    private void responseTokenError(ServletResponse response,RCode rCode) {
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setStatus(HttpStatus.OK.value());
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        try (PrintWriter out = httpServletResponse.getWriter()) {
            String data = new Gson().toJson(R.error(rCode));
            out.append(data);
        } catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }
}

整合shiro的配置,shiro可以帮我们做很多事情,比如创建realm、配置shiro过滤器工厂等。

@Configuration
public class ShiroConfig {

    @Bean("securityManager")
    public DefaultWebSecurityManager getManager(UserRealm realm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // 使用自己的realm
        manager.setRealm(realm);

        /*
         * 关闭shiro自带的session,详情见文档
         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);

        return manager;
    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt", new JWTFilter());
        factoryBean.setFilters(filterMap);

        factoryBean.setSecurityManager(securityManager);
        /*
         * 自定义url规则
         * http://shiro.apache.org/web.html#urls-
         */
        Map<String, String> filterRuleMap = new HashMap<>();
        // 所有请求通过我们自己的JWT Filter
        filterRuleMap.put("/**", "jwt");
        // 访问401和404页面不通过我们的Filter
        filterRuleMap.put("/admin/sys/user/login", "anon");
        filterRuleMap.put("/user/imgCode", "anon");
        //开放API文档接口
        filterRuleMap.put("/swagger-ui.html", "anon");
        filterRuleMap.put("/webjars/**","anon");
        filterRuleMap.put("/swagger-resources/**","anon");
        filterRuleMap.put("/v2/**","anon");
        //sql监控
        filterRuleMap.put("/druid/**","anon");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }
    /**
     * 下面的代码是添加注解支持
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        // https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

自定义realm,因为我没有连接数据库这里的用户名和密码都是写死的,根据业务需要可以连接数据库获取

@Service
public class UserRealm extends AuthorizingRealm {
    /**
     * 大坑!,必须重写此方法,不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }
    /**
     * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
    /**
     * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        if(JWTUtils.isExpire(token)){
            throw new AuthenticationException("token过期,请重新登入!");
        }
        // 解密获得username,用于和数据库进行对比
        String username = JWTUtils.getUsername(token);

        if (username == null) {
            throw new AuthenticationException("token错误,请重新登入!");
        }

        String name="admin";
        String pwd="3bcbb857c763d1429a24959cb8de2593";
        if (! JWTUtils.verify(token, name, pwd)) {

            throw new AuthenticationException("密码错误!");
        }

        return new SimpleAuthenticationInfo("activeUser", token, getName());
    }
}
public class JWTToken implements AuthenticationToken {

    // 密钥
    private String token;

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

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

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

jwt生成token

public class JWTUtils {
    /**
     * 过期时间6小时
     */
    private static final long EXPIRE_TIME = 6 * 60 * 60 * 1000;

    /**
     * 校验token是否正确
     *
     * @param token  密钥
     * @param secret 用户的密码
     * @return 是否正确
     */
    public static boolean verify(String token, String username, String secret) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     *
     * @return token中包含的用户名
     */
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 生成签名,2min后过期
     *
     * @param username 用户名
     * @param secret   用户的密码
     * @return 加密的token
     */
    public static String sign(String username, String secret) {
        try {
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            // 附带username信息
            return JWT.create()
                    .withClaim("username", username)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 判断过期
     *
     * @param token
     * @return
     */
    public static boolean isExpire(String token) {
        DecodedJWT jwt = JWT.decode(token);
        return System.currentTimeMillis() > jwt.getExpiresAt().getTime();
    }
}

使用md5进行加密

    /**
     * 密码加密
     * @return
     */
    public static String md5Encryption(String source,String salt){
        String algorithmName = "MD5";//加密算法
        int hashIterations = 1024;//加密次数
        SimpleHash simpleHash = new SimpleHash(algorithmName,source,salt,hashIterations);
        return simpleHash+"";
    }

重载类:用于结果返回

@Data
@AllArgsConstructor
@NoArgsConstructor
public class R {
    private Integer code ;
    private String msg;
    private Object data;



    public static R ok(Object data){
        return new R(RCode.SUCCESS.getCode(),
                RCode.SUCCESS.getMsg(),data);
    }
    public static R ok(){
        return R.ok(null);
    }
    public static R error(RCode rCode){
        return new  R(rCode.getCode(),rCode.getMsg(),null);
    }
    public static R error(String message){
        return new  R(RCode.SYS_ERROR.getCode(),message,null);
    }
    public static R error(){
        return R.error(RCode.ERROR);
    }
}

枚举类

@AllArgsConstructor
@Getter
public enum RCode {
    SUCCESS(0,"操作成功"),
    ERROR(1,"操作失败"),
    SYS_ERROR(500,"系统错误,请与管理员联系"),
    SYS_VALID_ERROR(501,"参数校验错误"),
    USER_EXIST(1001,"用户已存在"),
    USER_NOT_EXIST(1002,"用户不存在!"),
    VALID_ERROR(40001,"验证错误!"),
    INVALID_TOKEN(40410,"Token无效,您无权访问该接口"),
    AUTH_ERROR(40411,"用户名或密码不存在!"),
    NO_AUTH_ERROR(40412,"无权访问!"),
    QUERY_EXIST(1002,"此数据以重复")
    ;
    private Integer code;
    private String msg;

}

到这里就可以开始写controller了,写了一个登陆的接口和两个测试的接口

@RestController
@CrossOrigin
@RequestMapping("/admin/sys/user")
public class TestController {

    @GetMapping("/login")
    public R login(String username,String password){
        String MD5Password = MD5Utils.md5Encryption(username, password);
        System.out.println(MD5Password);
        String token= JWTUtils.sign(username, MD5Password);
        System.out.println(token);
        JWTToken jwtToken = new JWTToken(token);
        try {
            SecurityUtils.getSubject().login(jwtToken);
        } catch (AuthenticationException e) {
            return R.error("token 错误!");
        }
        Map<String,Object> maps = new HashMap<>();
        maps.put("token",token);
        return R.ok(maps);
    }

    @GetMapping("all")
    public String all(){
        return "all";
    }
	@GetMapping("list")
    public R list(){
        return R.ok("list");
    }
}

测试结果:

如果我不携带token是不能访问任何接口的,除登陆接口外

在这里插入图片描述

下面访问登陆接口,是可以访问的,并且返回的token令牌,我们可以拿着这个token去访问其他接口
在这里插入图片描述

带着token就可以正常访问了,有效期是6个小时
在这里插入图片描述

总结:
本人学习这个shiro+jwt也没几天很多原理也还没弄明白,讲的不明白只实现了效果,如过有什么地方说的不对还希望大佬指点一下。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值