2022.3.3 SpringBoot+Shiro+Jwt

SpringBoot+Shiro+Jwt

流程:
1、用户请求,不携带token,在JwtFilter处做判断,若没有token,则可以访问没有添加@RequiresAuthentication注解的方法,若有token,则如2所示
2、用户请求,携带token,就到JwtFilter中获取jwt,封装成JwtToken对象。然后使用JwtRealm进行认证
3、在JwtRealm中进行认证判断这个token是否有效,有效则可访问url

1、依赖

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

<dependency>
   <groupId>io.jsonwebtoken</groupId>
   <artifactId>jjwt</artifactId>
   <version>0.9.1</version>
</dependency>

2、创建JwtUtils

public class JwtUtils {

    private static final String secret="f4e2e52034348f86b67cde581c0f9eb5";
    private static final long expire=604800;
    private static final String header="Authorization";

    /**
     * 生成jwt token
     */
    public String generateToken(long userId) {
        Date nowDate = new Date();
        //过期时间
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);

        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(userId+"")
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    public Claims getClaimByToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        }catch (Exception e){
            log.debug("validate is token error ", e);
            return null;
        }
    }
    /**
     * token是否过期
     * @return  true:过期
     */
    public boolean isTokenExpired(Date expiration) {
        return expiration.before(new Date());
    }
}

3、JwtFilter

Jwt拦截器,会拦截所有请求,对请求进行过滤,后续会注册到shrio的配置文件中

public class JwtFilter extends AccessControlFilter {

    @Autowired
    JwtUtils jwtUtils;

    /**
     * isAccessAllowed()判断是否携带了有效的JwtToken
     * onAccessDenied()是没有携带JwtToken的时候进行账号密码登录,登录成功允许访问,登录失败拒绝访问
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        /**
         * 1. 返回true,shiro就直接允许访问url
         * 2. 返回false,shiro才会根据onAccessDenied的方法的返回值决定是否允许访问url
         *  这里先让它始终返回false来使用onAccessDenied()方法
         */
        log.warn("isAccessAllowed方法被调用");
        return false;
    }


    /**
     *
     * @param servletRequest
     * @param servletResponse
     * @return返回结果为true表明登录通过
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        /**
         *这个地方和前端约定,要求前端将jwtToken放在请求的Header部分
         *所以以后发起请求的时候就需要在Header中放一个Authorization,值就是对应的Token
         */
        log.warn("onAccessDenied方法被调用");
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        String token = request.getHeader("Authorization");
        if(token==null){//如果token为空的话,返回true,交给控制层@RequiresAuthentication进行判断;也会达到没有权限的作用
            return true;
        }
        JwtToken jwtToken = new JwtToken(token);
        try{
            /**
             * 进行登录处理
             * 委托 realm 进行登录认证
             * 所以这个地方最终还是调用AccountRealm进行的认证
             */
            getSubject(servletRequest,servletResponse).login(jwtToken);
        }catch (Exception e){

//            e.printStackTrace();
//            onLoginFail(servletResponse);
            return false;
        }
        //如果走到这里,那么就返回true,代表登录成功
        return true;
    }

    //登录失败要执行的方法
    private void onLoginFail(ServletResponse response) throws IOException{
        HttpServletResponse httpServletResponse = (HttpServletResponse)response;
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        httpServletResponse.getWriter().print("login error");
    }
}

4、创建JwtToken类

JwtToken类要继承AuthenticationToken类

//继承AuthenticationToken  要和AccountRealmh中的doGetAuthenticationInfo的参数类型保持一致
public class JwtToken implements AuthenticationToken {

    private String token;

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

    @Override//类似用户名
    public Object getPrincipal() {
        return token;
    }

    @Override//类似密码
    public Object getCredentials() {
        return token;
    }
}

5、创建AccountRealm类

用来对用户进行认证和授权

/**
 * //getName():返回一个唯一的Realm名字
 */
@Component
public class AccountRealm extends AuthorizingRealm {

    @Autowired
    JwtUtils jwtUtils;

    @Autowired
    IUserService userService;


    /**
     *  多重写一个support
     *  标识这个Realm是专门用来验证JwtToken
     * 不负责验证其他的token(UsernamePasswordToken)
     *
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String jwt = (String)authenticationToken.getPrincipal();
        String id = jwtUtils.getClaimByToken(jwt).getSubject();//获取jwt中关于用户的id
        User user = userService.getById(Long.valueOf(id));//查询用户
        if(user == null){
            throw new UnknownAccountException("用户不存在");
        }
        if(user.getStatus()==-1){
            throw new LockedAccountException("用户被锁定");
        }

        Claims claims =  jwtUtils.getClaimByToken(jwt);
        if(jwtUtils.isTokenExpired(claims.getExpiration())){
            throw new ExpiredCredentialsException("token过期,请重新登录");
        }
        AccountProfile accountProfile = new AccountProfile();//新建一个用户类,这里包括需要认证的用户信息,当然也可以直接用user
        BeanUtil.copyProperties(user,accountProfile);//将user的属性copy到accountProfile中,其实这里也可以用user
        return new SimpleAuthenticationInfo(accountProfile,jwt,getName());
    }
}

6、ShiroConfig

/**
 * shiro启用注解拦截控制器
 * shiro的三个重要配置
 * 1、Realm
 * 2、DefaultWebSecurityManager
 * 3、ShiroFilterFactoryBean
 */
@Configuration
public class ShiroConfig {

    @Autowired
    JwtFilter jwtFilter;

//    @Bean
//    public SubjectFactory subjectFactory(){
//        return new DefaultSubjectFactory();
//    }

    @Bean//把AccountRealm注入到spring容器中
    public Realm realm(){
        return new AccountRealm();
    }

    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        //关闭shiroDao功能
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        // 不需要将 ShiroSession 中的东西存到任何地方(包括 Http Session 中)
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        //securityManager.setSubjectFactory(subjectFactory());
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager());
        /**
         *  添加jwt过滤器,并在下面注册
         *  也就是将jwtFilter注册到shiro的Filter中
         *  指定除了login之外的请求都先经过jwtFilter
         */
        HashMap<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt",new JwtFilter());
        shiroFilter.setFilters(filterMap);


        /**
         * 拦截器
         */
        LinkedHashMap<String, String> filtRuleMap = new LinkedHashMap<>();
        filtRuleMap.put("/login","anon");
        filtRuleMap.put("/**","jwt");
        shiroFilter.setFilterChainDefinitionMap(filtRuleMap);
        return shiroFilter;

    }


    /**
     * 下面的的bean是为了解决@RequiresAuthentication注解不生效的配置
     */
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new
                DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean("authorizationAttributeSourceAdvisor")
    public AuthorizationAttributeSourceAdvisor
    authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new
                AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

7、测试类

@RestController
public class AccountController {

    @Autowired
    JwtUtils jwtUtils;

    @Autowired
    IUserService userService;
    @PostMapping("/login")
    public Result login(@RequestBody @Validated LoginDto loginDto, HttpServletResponse response){
        Result result = new Result();
        User user = userService.getOne(new QueryWrapper<User>().eq("username", loginDto.getUsername()));
        if(user==null){
            return Result.fail("用户名不存在");
        }

        if(!user.getPassword().equals(SecureUtil.md5(loginDto.getPassword()))){
            return Result.fail("用户密码错误");
        }

        String token = jwtUtils.generateToken(user.getId());
        response.setHeader("Authorization",token);
        response.setHeader("Access-control-Expost-Headers","Authorization");
        return Result.succ(MapUtil.builder().put("token",token).map());
    }

    @RequiresAuthentication//访问该接口就需要有jwt认证
    @GetMapping("/logout")
    public Result logout(){
        SecurityUtils.getSubject().logout();//退出登录
        return Result.succ(null);
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值