Shiro框架和JWT

目录

shiro简介

1.认证

2.授权

3.shiro靠什么做认证与授权

JWT简介

创建JWTUtil工具类

令牌封装成对象

AuthenticationToken类

AuthorizingRealm类

刷新令牌的新机制

 创建存储令牌的媒介类

创建过滤器

创建ShiroConfig


shiro简介

shiro是java非常有名的认证与授权的框架,使用JavaEE中JAAS功能。设计简单,SpringSecurity必须使用spring项目中。

1.认证

核验用户身份,认证登录,登录后Shiro要记录用户成功登录的凭证

2.授权

比再认证更加精细度的划分用户的行为。比如学生和班主任,可以可以改成绩,一个不可以改成绩。

3.shiro靠什么做认证与授权

主要利用HttpSession或者Redis存储用户的登录平成,以及角色或者身份信息。然后利用过滤器(Filter),对每个Http请求过滤,检查请求对应的HttpSessionRedis中的认证与授权。如果用户没有登录,或权限不够,那么Shiro就会向和护短返回错误信息。

JWT简介

主要做单点登录

未使用JWT,三个节点三个tomcat做负载均衡,A节点登录,B节点无法获取用户信息

使用JWT,对登录凭证进行加密,并保存到客户端,并叫做令牌,每次发送请求的时候,都把令牌上传到服务器,

 JWT兼容更多的客户端

传统的HttpSession依靠浏览器的Cookie存放SessionID,所以要求客户端浏览器。

但现在javaweb系统,客户端可以是浏览器,APP,小程序,以及物联网设备,为了让javaweb都访问到项目,引用JWT技术。

jwt的token是纯字符串,对保存没要求,只要客户端发起请求的时候带上token即可。可以使用SQLite存储Token数据

创建JWTUtil工具类

导入依赖库

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.13</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

定义密钥的和过期时间

在application.yml中配置

emos:
  jwt:
    #密钥
    secret: abc123456
    #令牌过期时间(天)
    expire:  5
    #令牌缓存时间(天数)
    cache-expire: 10

生成令牌并验证令牌的有效性

令牌的生成需要密钥、过期时间、用户ID

配置类

@Component
@Slf4j
public class JwtUtil {
    @Value("${emos.jwt.secret}")
    private String secret;
    @Value("${emos.jwt.expire}")
    private int expire;

    public String createToken(int userId){
//        当前日期偏移5天
        Date date = DateUtil.offset(new Date(), DateField.DAY_OF_YEAR, 5);
//        加密算法,调用静态工厂方法进行调用
        Algorithm algorithm=Algorithm.HMAC256(secret);
//        创建内部类
        JWTCreator.Builder builder= JWT.create();
        String token = builder.withClaim("userId", userId).withExpiresAt(date).sign(algorithm);
        return token;
    }
//    获取user的用户ID
    public int getUserId(String token){
//        创建解码的对象
        DecodedJWT jwt = JWT.decode(token);
        int userId = jwt.getClaim("userId").asInt();
        return userId;
    }
//    验证令牌字符串的有效性
    public void verifierToken(String token){
        Algorithm algorithm=Algorithm.HMAC256(secret);
        JWTVerifier verifier = JWT.require(algorithm).build();
//        验证方法回抛出runtime的异常
        verifier.verify(token);
    }

}

令牌封装成对象

主要4个类

AuthenticationToken类

public class OAuth2Token implements AuthenticationToken {

    private String token;

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

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

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

AuthorizingRealm类

@Component
public class OAuth2Realm extends AuthorizingRealm {
    @Autowired
    private JwtUtil jwtUtil;

//    传入封装好的令牌对象
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof OAuth2Token;
    }
//授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//       创建认证对象
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        //TODO 查询用户的全下班列表
        //TODO 把权限列表添加到info对象中
        return info;
    }
//认证方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//创建授权对象
//        todo 从令牌中获取userID,然后检测该账户是否被冻结
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo();
        //todo 往info对象中添加用户信息,token字符串
        return info;
    }
}

刷新令牌的新机制

为什么刷新令牌?

令牌生成就保存到客户端,即使用户一直使用系统们也不会重新生成令牌,令牌过期,用户必须重新登陆,令牌应该自动续期

解决办法:

1).双令牌机制

设置长短日期的令牌,短时期令牌生效就用长令牌

2).缓存令牌机制

令牌缓存到redis上面,缓存的令牌过期时间是客户端令牌的一倍,

如果客户端令牌过期,缓存令牌没有过期,则生成新的令牌,

如果客户端令牌过期,缓存令牌也过期,则需要重新登陆

客户端如何更新令牌?

 如何在响应当中添加令牌?

 创建存储令牌的媒介类

//媒介类
@Component
public class ThreadLocalToken {
    private ThreadLocal<String> local=new ThreadLocal();
    public void setToken(String token){
        local.set(token);
    }

    public String getToken(){
        return local.get();
    }
    
    public void clear(){
        local.remove();
    }
}

创建过滤器

OAuth2Filter类,判断那些那些请求应该被shiro处理,如果是options请求直接放行,提交application/json数据,请求被差分成option和post两次,其余所有请求都要被shiro处理。

判断用户Token是真过期还是假过期,真过期,返回提示信息,让用于重新登录,假的过期,就生成新的令牌,返回客户端。

存储新令牌,ThreadLocalToken和redis

OAuth2Filter类
@Component
//创建多例对象prototype表示每次获得bean都会生成一个新的对象
@Scope("prototype")
public class OAuth2Filter extends AuthenticatingFilter {
    @Autowired
    private ThreadLocalToken threadLocalToken;

    @Value("emos.jwt.cache-expire")
    private int cacheExpire;

    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private RedisTemplate redisTemplate;

    /*
    拦截请求后,用于把令牌字符串封装成令牌对象
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        String token=getRequestToken(req);
        if(StrUtil.isBlank(token)){
            return null;
        }
        return new OAuth2Token(token);
    }
    /*
    拦截请求,判断请求是否需要被shiro处理
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest req = (HttpServletRequest) request;
        if(req.getMethod().equals(RequestMethod.OPTIONS.name())){
            return true;
        }
        return false;
    }
/*
被拦截之后执行
 */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req=(HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
//        响应字符集
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
//        允许跨域请求
        resp.setHeader("Access-Control-Allow-Credentials", "true");
        resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
//        请求threadlocal
        threadLocalToken.clear();
        String token=getRequestToken(req);
        if (StrUtil.isBlank(token)){
            resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
            resp.getWriter().print("无效令牌");
            return false;
        }
        try {
            jwtUtil.verifierToken(token);
        } catch (TokenExpiredException e) {
            if(redisTemplate.hasKey(token)){
                redisTemplate.delete(token);
                int userId = jwtUtil.getUserId(token);
                token = jwtUtil.createToken(userId);
                redisTemplate.opsForValue().set(token,userId+"",cacheExpire ,TimeUnit.DAYS);
                threadLocalToken.setToken(token);
            }else {
                resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
                resp.getWriter().print("令牌已过期");
                return false;
            }
        }catch (JWTDecodeException e){
            resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
            resp.getWriter().print("伪造无效的令牌");
            return false;
        }
//        间接调用AuthorizingRealm类,认证和授权
        boolean bool = executeLogin(request, response);
        return bool;
    }
//登录失败返回认证消息
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletRequest req=(HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
//        响应字符集
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
//        允许跨域请求
        resp.setHeader("Access-Control-Allow-Credentials", "true");
        resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
        resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
        try {
            resp.getWriter().print(e.getMessage());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return false;
    }

    @Override
    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        super.doFilterInternal(request, response, chain);
    }

    private String getRequestToken(HttpServletRequest request){
        String token=request.getHeader("token");
        if(StrUtil.isBlank(token)){
            token = request.getParameter("token");
        }
        return token;
    }
}

创建ShiroConfig

把Filter和Realm添加到Shiro框架

创建四个对象返回给springboot

1.SecurityManager 用于封装realm对象

2.ShiroFilterFactoryBean 1).用于封装Filter对象   2).设置Filter拦截路径

3.LifecycleBeanPostProcessor 管理shro对象生命周期

4.AuthorizationAttributeSourceAdvsor  1).Aop切面类  2).web方法执行前,权限验证

import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;


@Configuration
public class shiroConfig {
//    封装Realm对象
    @Bean("securityManager")
    public SecurityManager securityManager(OAuth2Realm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        securityManager.setRememberMeManager(null);
        return securityManager;
    }

//    用于封装Filter对象
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shirofilter(SecurityManager securityManager,OAuth2Filter filter){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
//        oauth过滤
        Map<String, Filter> map = new HashMap<>();
        map.put("oauth2",filter);
        shiroFilter.setFilters(map);
//       设置Filter拦截路径
//        如果是anon表示不需要拦截
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/webjars/**", "anon");
        filterMap.put("/druid/**", "anon");
        filterMap.put("/app/**", "anon");
        filterMap.put("/sys/login", "anon");
        filterMap.put("/swagger/**", "anon");
        filterMap.put("/v2/api-docs", "anon");
        filterMap.put("/swagger-ui.html", "anon");
        filterMap.put("/swagger-resources/**", "anon");
        filterMap.put("/captcha.jpg", "anon");
        filterMap.put("/user/register", "anon");
        filterMap.put("/user/login", "anon");
        filterMap.put("/test/**", "anon");
        filterMap.put("/**", "oauth2");
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }
//    管理shiro的生命周期
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();

    }
//    @Bean不写默认也是类的首字母小写
    @Bean("authorizationAttributeSourceAdvisor")
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

创建AOP切面类

拦截所有web方法返回值

判断是否刷新生成新令牌

1)检查ThreadLocal中是否保存令牌

2)把新的令牌保存到R对象中

@Aspect
@Component
public class TokenAspect {
    @Autowired
    private ThreadLocalToken threadLocalToken;

    @Pointcut("execution(public * com.qing.emos.wx.controller.*.*(..)))")
    public void aspect() {

    }

    @Around("aspect()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
//        获取拦截对象返回的执行结果
        R r=(R)point.proceed();
        String token = threadLocalToken.getToken();
        if(token!=null){
            r.put("token",token);
            threadLocalToken.clear();
        }
        return r;
    }
}

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
课程简介:历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring BootSpring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值