IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> springboot+shiro+jwt -> 正文阅读

[Java知识库]springboot+shiro+jwt


# 1.介绍 ## 1.shiro
  • Apache Shiro是一个 java 的安全(权限)框架。
  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE环境,也可以用在javaEE环境。
  • Shiro可以完成,认证、授权、加密、会话管理、Web集成、缓存等。

2.jwt

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。

我们利用一定的编码生成 Token,并在 Token 中加入一些非敏感信息,将其传递。

2.使用

1.引入依赖

<!-- web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.7.1</version>
</dependency>
<!-- jwt -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.2.0</version>
</dependency>

2.编写代码

1.ShiroConfig

@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        // 添加 shiro 的内置过滤器
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("jwt", new JwtFilter());
        bean.setFilters(filterMap);
        // 设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        bean.setUnauthorizedUrl("/unauthorized"); // 无权限时跳转
        Map<String, String> filterRuleMap = new HashMap<>();
        // 所有的请求通过我们自己的jwt filter
        filterRuleMap.put("/**", "jwt");
        // 访问 /unauthorized/** 不通过jwt filter
        filterRuleMap.put("/unauthorized", "anon");
        filterRuleMap.put("/sys-user/login", "anon");
        filterRuleMap.put("/captcha", "anon");

        bean.setFilterChainDefinitionMap(filterRuleMap);
        return bean;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("UserRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        securityManager.setRealm(userRealm);

        /*
         * 关闭shiro自带的session
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }

    /**
     *  开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启aop注解支持
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    // 自定义realm
    @Bean(name = "UserRealm")
    public UserRealm userRealm(){
        return new UserRealm();
    }
}

2.UserRealm

public class UserRealm extends AuthorizingRealm {
    @Autowired
    SysUserServiceImpl sysUserService;
    @Autowired
    SysRoleServiceImpl sysRoleService;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 授权
        // 1.获取当前用户信息
        Subject subject = SecurityUtils.getSubject();
        SysUser sysUser = (SysUser)subject.getPrincipal();
        System.out.println("当前授权用户信息:" + sysUser);

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 1.获取当前用户的角色
        ArrayList<SysRole> userRoles = sysUserService.getUserRoles(sysUser.getId());
        System.out.println("当前用户的角色列表:" + userRoles);
        Set<String> userRoleSet = new HashSet<>();
        userRoles.forEach(userRole -> userRoleSet.add(userRole.getCode()));
        // 2.获取角色拥有的权限
        ArrayList<Long> longRoles = new ArrayList<>();
        userRoles.forEach(userRole -> longRoles.add(userRole.getId()));
        ArrayList<SysMenu> userPermission = sysRoleService.getUserPermission(longRoles);
        System.out.println("当前用户的角色权限列表:" + userPermission);
        Set<String> userPermissionSet = new HashSet<>();
        userPermission.forEach(SysMenu -> userRoleSet.add(SysMenu.getPerms()));

        info.setRoles(userRoleSet);
        info.setStringPermissions(userPermissionSet);
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 认证
        // 1.获取 token
        String token = (String) authenticationToken.getCredentials();
        // 解密获得username,用于和数据库进行对比
        String username = JwtUtil.getUsername(token);
        if (username == null || !JwtUtil.verify(token, username)) {
            throw new AuthenticationException("token认证失败!");
        }
        SysUser sysUser = sysUserService.getUserByName(username);
        if (sysUser == null) {
            throw new AuthenticationException("用户不存在");
        }
        System.out.println("登录成功");
        return new SimpleAuthenticationInfo(sysUser, token, "myRealm");
    }
}

3.JwtFilter

@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // 判断请求的请求头上是否带上 "Token"
        if (((HttpServletRequest) request).getHeader("token") != null) {
            // 如果存储,则进入executeLogin方法执行登录,检查 token 是否正确
            try {
                executeLogin(request, response);
                return true;
            } catch (AuthenticationException e) {
                // token 错误
                responseError(response);
            }
        }
        // 如果请求头不存在 token,则可能是执行登录操作,或者是游客状态登录,无需检查token,直接返回true
        return true;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Token");
        JwtToken jwtToken = new JwtToken(token);
        // 提交给realm进行登录,如果错误他会抛出一行并捕获。
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常则代表登入成功,返回true
        return true;
    }

    /**
         * 将非法请求跳转到 /unauthorized
     */
    private void responseError(ServletResponse response) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.sendRedirect("/unauthorized");
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

}

4.Controller

@Autowired
private Producer producer;
/*
* 图片验证码
* */
@GetMapping("/captcha")
public Result captcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String code = producer.createText();
    String key = UUID.randomUUID().toString(); // 生成uuid
    BufferedImage image = producer.createImage(code);
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ImageIO.write(image, "jpg", outputStream);
    // 图片转为base64
    BASE64Encoder encoder = new BASE64Encoder();
    String str = "data:image/jpeg;base64,";
    String base64Img = str + encoder.encode(outputStream.toByteArray());

    // 存储到 redis 中
    redisUtil.hset(Const.CAPTCHA_KEY, key, code, 120);
    log.info("验证码 -- {} -- {}", key, code);
    return Result.succ(
            MapUtil.builder()
                    .put("token", key)
                    .put("base64Img", base64Img)
                    .build()
    );
}
// 登录
@PostMapping("/login")
public Result login(@RequestBody JSONObject jsonObject, HttpServletRequest request, HttpServletResponse response){
    System.out.println();
    // 验证码校验
    String codeToken = (String) jsonObject.get("token");
    String code = (String) jsonObject.get("code");
    if (codeToken == null || code == null) {
        return Result.succ(400, "验证码不能为空", null);
    }
    if (!code.equals(redisUtil.hget(Const.CAPTCHA_KEY, codeToken))) {
        return Result.succ(400, "验证码错误", null);
    }


    String username = (String) jsonObject.get("username");
    String password = (String) jsonObject.get("password");

    // 2.通过用户名,从数据库取用户信息
    SysUser user = sysUserService.getUserByName(username);
    if(user == null) {
        return Result.succ(400, "用户不存在", null);
    }
    if(!user.getPassword().equals(password)) {
        return Result.succ(400, "密码错误", null);
    }

    // 一次性使用
    redisUtil.hdel(Const.CAPTCHA_KEY, codeToken);

    response.setHeader("Token", JwtUtil.createToken(username));
    return Result.succ(200, "登录成功", "");
}

5.JwtUtil

public class JwtUtil {
    // 过期时间 24 小时
    private static final long EXPIRE_TIME = 60 * 24 * 60 * 1000;
    // 密钥
    private static final String SECRET = "SHIRO+JWT";

    /**
     * 生成 token, 5min后过期
     *
     * @param username 用户名
     * @return 加密的token
     */
    public static String createToken(String username) {
        try {
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            // 附带username信息
            return JWT.create()
                    .withClaim("username", username)
                    //到期时间
                    .withExpiresAt(date)
                    //创建一个新的JWT,并使用给定的算法进行标记
                    .sign(algorithm);
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    /**
     * 校验 token 是否正确
     *
     * @param token    密钥
     * @param username 用户名
     * @return 是否正确
     */
    public static boolean verify(String token, String username) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            //在token中附带了username信息
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            //验证 token
            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;
        }
    }
}

3.测试

1.请求验证码
获取到 token:“24ce6a25-0415-4ce5-82fc-f71a734fe775”
2.请求登录接口
username 用户名
password 密码
code 验证码
token 验证码接口给到jwt,用来验证验证码的正确
3.登录成功后,response的请求头会多一个token
之后的请求,在请求头上加上token就会进行权限验证

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-12-13 12:40:04  更:2021-12-13 12:42:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:24:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码