shiro+jwt进行认证和授权的解决方案代码实例

jwt和shiro框架就不多介绍了,直接上实例代码吧。目前测试可正常登录、获得用户角色、访问接口时根据@requireRoles进行拦截。

token类


import org.apache.shiro.authc.AuthenticationToken;
//一般的登陆只需要校验账号和密码两个要素,默认的UsernamePasswordToken就能满足需求
//这个就类似UsernamePasswordToken
//在JwtRealm中的授权部分,可以使用JwtUtil.decode(jwt).get("username")获取到username,使用username去数据库中查找到对应的权限,然后将权限赋值给这个用户就可以实现权限的认证了


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;
    }
}

自定义realm

@Slf4j
public class JwtRealm extends AuthorizingRealm {
    
    
    /*
     * 多重写一个support
     * 标识这个Realm是专门用来验证JwtToken
     * 不负责验证其他的token(UsernamePasswordToken)
     * */
    @Override
    public boolean supports(AuthenticationToken token) {
    
    
        //这个token就是从过滤器中传入的jwtToken
        return token instanceof JWTToken;
    }
    @Autowired
    private IInowRoleUserService roleUserService;
    @Autowired
    private IInowRoleService roleService;
    @Autowired
    private IInowUserService userService;
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        log.info("=========执行了授权===========================");
        InowUser userName = (InowUser) principalCollection.getPrimaryPrincipal();
        InowUser inowUser = userService.selectOneByName(userName.getUserName());
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        final InowRoleUser roleuser = roleUserService.getOne(new LambdaQueryWrapper<InowRoleUser>().eq(InowRoleUser::getUserId, inowUser.getUserId()));
        List<String> inowRoleList = new ArrayList<>();
        inowRoleList.add(roleService.getOne(new LambdaQueryWrapper<InowRole>().eq(InowRole::getId,roleuser.getRoleId())).getRoleName());
        simpleAuthorizationInfo.addRoles(inowRoleList);
        //添加用户角色,用于@requireroles注解
        return simpleAuthorizationInfo;
    }

    //认证
    // 这里的 token是从 JWTFilter 的 executeLogin 方法传递过来的(请求头的token)
    // 只要调用了subject.login(token)方法,就会进入到realm的doGetAuthenticationInfo内。
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
    
    

       String token = (String)auth.getCredentials();
     log.info("从auth获得的Token"+token);
       String userName = JWTUtil.getUsername(token);

        //用户不存在(这个在登录时不会进入,只有在token校验时才有可能进入)
        if(userName == null)
        {
    
    
            throw new UnknownAccountException();
        }
        InowUser user = userService.selectOneByName(userName);
        if(Objects.isNull(user))
        {
    
    
            throw new UnknownAccountException();
        }
        String salt = "666666"; //自己随便写的一个加密盐
        Md5Hash md5Hash = new Md5Hash(user.getUserId(), salt, 1024);
if(!JWTUtil.verify(token,userName, md5Hash.toHex()))//学号当做用户密码
        {
    
    

            throw new IncorrectCredentialsException();
        }
        //toke过期
        if(JWTUtil.isExpire(token)){
    
    
            throw new ExpiredCredentialsException();
        }
     
        return new SimpleAuthenticationInfo(user, token, getName());
    }

}

自定义的jwtFilter用于访问拦截:

@Slf4j
//@Component("jwtFilter")
//去掉@Component注解,让filter不交给springbean管理,因为这会导致shiro内置的anon过滤器失效,具体原因还未找出
public class JwtFilter extends BasicHttpAuthenticationFilter {
    
    

    /**
     * 进行token的验证
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    
    
        //在请求头中获取token
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Authorization"); //前端命名Authorization
        //token不存在
        if(token == null || "".equals(token)){
    
    
Result result = new Result();
            result.setCode(400);
            result.setMsg("token为空,无法访问");
            out(response,result);
   return false;

        }

        //token存在,进行验证
       JWTToken jwtToken = new JWTToken(token);
        try {
    
    
            SecurityUtils.getSubject().login(jwtToken);  //通过subject,提交给myRealm进行登录验证
            return true;
        } catch (ExpiredCredentialsException e){
    
    
            Result result = new Result();
            result.setCode(400);
            result.setMsg("登录已经过期");
out(response,result);
            e.printStackTrace();
            return false;
        } catch (ShiroException e){
    
    
            // 其他情况抛出的异常统一处理,由于先前是登录进去的了,所以都可以看成是token被伪造造成的
         Result res = new Result();



            res.setMsg("无效token");
            out(response,res);
            e.printStackTrace();
            return false;
        }
    }

    /**
     * json形式返回结果token验证失败信息,无需转发
     */
    private void out(ServletResponse response, Result res) throws IOException {
    
    
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        ObjectMapper mapper = new ObjectMapper();
        String jsonRes = mapper.writeValueAsString(res);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        httpServletResponse.getOutputStream().write(jsonRes.getBytes());
    }

    /**
     * 过滤器拦截请求的入口方法
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    
    
        try {
    
    
            return executeLogin(request, response);  //token验证
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * isAccessAllowed()方法返回false,即认证不通过时进入onAccessDenied方法
     */
//    @Override
//    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    
    
//        return super.onAccessDenied(request, response);
//    }

    /**
     * token认证executeLogin成功后,进入此方法,可以进行token更新过期时间
     */
//    @Override
//    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
    
    

//    }
}

shiroconfig


@Configuration
public class ShiroConfig {
    
    

    public logOutFilter mylogoutFilter()
    {
    
    
        logOutFilter MylogoutFilter = new logOutFilter();
        MylogoutFilter.setLoginUrl("login");//设置退出后重定向的跳转地址
        return MylogoutFilter;
    }

    /**
     * 注入Shiro过滤器链配置
     * 注入安全服务配置
     * 注入自定义jwt过滤器
     *
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Autowired @Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager,
                                                         @Autowired ShiroFilterChainDefinition definition
                                                       ){
    
    
        JwtFilter jwtFilter = new JwtFilter();
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(defaultWebSecurityManager);//设置安全管理器
        //可以添加shiro的内置过滤器
        //anon:无需认证就可以访问 authc:必须认证了才能用 oerms:拥有对某个资源的权限才能访问
        bean.setLoginUrl("/login");//设置登录路径
        //bean.setUnauthorizedUrl("/unlogin");

        Map<String, Filter> filterMap = new HashMap<>();
        // 注销成功,则跳转到指定页面
        filterMap.put("logout", mylogoutFilter());
        filterMap.put("anon", new AnonymousFilter());

        filterMap.put("jwt",jwtFilter);
bean.setFilters(filterMap);

   

 bean.setFilterChainDefinitionMap(definition.getFilterChainMap());
        return bean;
    }

    /**
     * 自定义jwt过滤器
     *
     * @return
     */
    //@Bean
    public JwtFilter jwtFilter(){
    
    
        return new JwtFilter();
    }


    /**
     * 定义拦截器链,所有请求都经过自定义的jwt过滤器
     *
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition(){
    
    
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        Map<String,String> map = new LinkedHashMap<>();
        map.put("/login","anon");
        map.put("/swagger-ui.html","anon");
        map.put("/doc.html","anon");
        map.put("/**","jwt");
        definition.addPathDefinitions(map);
        return definition;
    }

    /**
     * 自定义Realm
     *
     * @return
     */
    @Bean
    public JwtRealm myUserRealm(){
    
    
        JwtRealm userRealm = new JwtRealm();
        return userRealm;
    }

    /**
     * 开启认证授权注解
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Autowired DefaultWebSecurityManager securityManager){
    
    
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    
    
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    //DefaultAdvisorAutoProxyCreator:
    //BeanPostProcessor实现,它根据当前BeanFactory中的所有候选Advisor创建 AOP 代理。
  
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
    
    
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制指定注解的底层实现使用 cglib 方案,防止重复代理和可能引起代理出错的问题
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
    /**
     * 配置安全服务机制,注入自定义Realm,关闭了shiro的默认session机制
     *
     * @param
     * @return
     */

        /*
         * 关闭shiro自带的session,详情见文档
         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
         */
    /*
     * a. 告诉shiro不要使用默认的DefaultSubject创建对象,因为不能创建Session
     * */
    @Bean
    public SubjectFactory subjectFactory() {
    
    
        return new JwtDefaultSubjectFactory();
    }

    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(@Autowired JwtRealm myRealm) {
    
    
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        // 关闭 ShiroDAO 功能
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        // 不需要将 Shiro Session 中的东西存到任何地方(包括 Http Session 中)
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        //禁止Subject的getSession方法
        securityManager.setSubjectFactory(subjectFactory());
        return securityManager;
    }
    }

controller

  @ApiOperation("登录接口")
    @GetMapping
    public Result userLogin(@ApiParam(name="userId",value="用户学号",required=true)
                            @RequestParam(value = "userId",required = true)String userId,
                            @ApiParam(name="userName",value="用户姓名",required=true)
    @RequestParam(value = "userName",required = true)String userName) {
    
    

        // 1、获取Subject实例对象


        //根据用户名获取正确用户信息
    InowUser user = userService.selectOneByName(userName);
        if(user == null)
        {
    
    
            return Result.fail("无效用户,请检查您的姓名");
        }

        //盐 + 输入的密码(注意不是用户的正确密码) + 1024次散列,作为token生成的密钥
        String salt = "666666";
        Md5Hash md5Hash = new Md5Hash(userId, salt, 1024);

        //生成token字符串
        String token = JWTUtil.getJwtToken(userName, md5Hash.toHex());   //toHex转换成16进制,32为字符
        log.info("从controller生成的Token"+token);
        JWTToken jwtToken = new JWTToken(token);


        Subject currentUser = SecurityUtils.getSubject();


            // 4、认证
            try {
    
    
                currentUser.login(jwtToken);// 传到Realm类中的方法进行认证 进入 AuthenticationInfo方法


                //session.setAttribute("username", userName);
                InowUser nowUser = (InowUser) currentUser.getPrincipal();
                InowUserVo userVo = new InowUserVo();
                BeanUtils.copyProperties(nowUser,userVo);
                userVo.setToken(token);
                return Result.success(userVo);
            }
            catch (AuthenticationException e) {
    
    
                String msg = null;

                if (StringUtils.isNotEmpty(e.getMessage())) {
    
    
                   msg = e.getMessage();
                }
                return Result.fail("用户名与学号不匹配!");
            }
 //   }
    }

vo对象

在vo中放了token属性的成员属性

@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class InowUserVo implements Serializable {
    
    
    private static final long serialVersionUID = 1L;
    /** 学号或者教师id */
    @NotBlank(message = "id不能为空!")
    @ApiModelProperty(value="用户id,学号或教师号",name="id",example="2020214283")
    private String userId;
    private Integer id;
    /** 姓名 */
    @NotBlank(message = "姓名不能为空!")
    @ApiModelProperty(value="用户姓名",name="userName",example="张三")
    private String userName;

    /** 学院 */
    @ApiModelProperty(value="用户学院",name="userAcademy",example="计算机与信息学院")
    private String userAcademy;

    /** 微信id */
    @ApiModelProperty(value="微信id",name="wechatId",example="微信id")
    private Long wechatId;

    @ApiModelProperty(value="性别",name="userSex",example="1男,2女")
    private byte userSex;
    @ApiModelProperty(value="最后登陆时间",name="loginDate")
    private Date loginDate;
    @ApiModelProperty(value="用户角色",name="roleName")
    private String roleName;
private String token;
}

测试

登录接口:
在这里插入图片描述

访问一个标有注解@RequireRoles(“admin”)的方法:
在不加token时返回结果:
在这里插入图片描述
加一个没有Admin角色的Token访问结果:
在这里插入图片描述
用有admin角色的用户token访问结果:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41358574/article/details/121647445