springboot+shiro简单token认证

前言

最后调用过程参考了 :
https://blog.csdn.net/long270022471/article/details/62423286

最近可能要用到,所以看了看shiro.

结合人人开源的管理后端代码以及其他大佬的写的有关shiro的博文,自己整理了一下避免以后忘记了.
其中代码中我加了很多注释,是个人的通俗理解, 可能不正确, 也欢迎大家指出共同进步.

Shiro的功能

在这里插入图片描述

  • Authentication:身份认证,验证用户是否拥有某个身份。
  • Authorization: 权限校验,验证某个已认证的用户是否拥有某个权限。确定“谁”可以访问“什么”。
  • Session Management:会话管理,管理用户登录后的会话,
  • Cryptography:加密,使用密码学加密数据,如加密密码。
  • Web Support:Web支持,能够比较轻易地整合到Web环境中。
  • Caching:缓存,对用户的数据进行缓存,
  • Concurrency:并发,Apache Shiro支持具有并发功能的多线程应用程序,也就是说支持在多线程应用中并发验证。
  • Testing:测试,提供了测试的支持。
  • Run as :允许用户以其他用户的身份来登录。
  • Remember me :记住我

依赖包

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

简单实现

在这里插入图片描述

共需要5个文件

CustomRealm: 自定义的认证源, 身份认证和权限认证

OAuthToken: token实体类

ShiroConfig: shiro的一些配置,相对于之前的xml配置。包括:过滤的文件和权限,密码加密的算法,其用注解等相关功能

TestFilter 自定义过滤器

TokenGenerator token生成器, 测试功能流程的话可以不用这个, 实际中生成token的逻辑

自定义令牌实体类

package com.atguigu.gulimall.shiro_demo.config;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * @author zhangshaokun
 * @date 2021/3/11
 * 自定义令牌实体类
 */
public class OAuthToken implements AuthenticationToken {
        private String token;

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

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

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

token生成器

package com.atguigu.gulimall.shiro_demo.demo;

import java.security.MessageDigest;
import java.util.UUID;

/**
 * 生成token
 *
 */
public class TokenGenerator {

    public static String generateValue() {
        return generateValue(UUID.randomUUID().toString());
    }

    private static final char[] hexCode = "0123456789abcdef".toCharArray();

    public static String toHexString(byte[] data) {
        if(data == null) {
            return null;
        }
        StringBuilder r = new StringBuilder(data.length*2);
        for ( byte b : data) {
            r.append(hexCode[(b >> 4) & 0xF]);
            r.append(hexCode[(b & 0xF)]);
        }
        return r.toString();
    }

    public static String generateValue(String param) {
        try {
            MessageDigest algorithm = MessageDigest.getInstance("MD5");
            algorithm.reset();
            algorithm.update(param.getBytes());
            byte[] messageDigest = algorithm.digest();
            return toHexString(messageDigest);
        } catch (Exception e) {
            // 这个可具体更改,现在做例子不动了,可以自定义异常类进行抛出提醒
            throw new RuntimeException("生成Token失败", e);
        }
    }
}

CustomRealm类

自定义认证源

package com.atguigu.gulimall.shiro_demo.demo;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author zhangshaokun
 * @date 2021/3/11
 */
@Component
public class CustomRealm extends AuthorizingRealm {


    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof OAuthToken;
    }

    /**
     * 权限认证,即登录过后,每个身份不一定,对应的所能看的页面也不一样
     * */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //这个里面存储的是主要身份信息,来源便是下面身份认证最后存入的第一个参数
        Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
        //利用这些信息,便能从数据库或者其他位置获取权限列表了,具体实现略
        //获取用户的权限列表,存入set中
        Set<String> permsSet = new HashSet<>();

        //授权信息实例
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //传入一个set
        info.setStringPermissions(permsSet);
        return info;
    }


    /**
     * 身份认证。即登录通过账号和密码验证登陆人的身份信息
     * */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取token
        String token = (String) authenticationToken.getPrincipal();
        //通过token获取用户信息, 比如token和用户信息存储在redis或数据库, 那么便根据token去查询
        //这里用map模拟数据库
        Map<String, String> token_value = new HashMap<>();
        token_value.put("6110e95350b2af23be9c70b93d0c118f","张三");
        token_value.put("token2","李四");
        //判断是否读取到信息
        String value = token_value.get(token);
        //如果没有查询到信息,或者token设置的时间已经过期
        if (StringUtils.isEmpty(value)){
            System.out.println("token失效,请重新登录");
            throw new IncorrectCredentialsException("token失效,请重新登录");
        }
        //token有效, 就查询用户的详细信息
        //还可以在做一些判断,比如用户有没有被注销,被封禁等等
        //具体实现略
        String str = "";  //这个只是临时传参避免报错的
        // 将用户信息(具体类型看需求,可以是简单的string也可以是对象,要方便上面权限认证是容易获取用户信息), token 传入
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(str, token, getName());
        return info;
    }
}

ShiroConfig类

package com.atguigu.gulimall.shiro_demo.demo;


import org.apache.shiro.mgt.SecurityManager;
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 javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author zhangshaokun
 * @date 2021/3/11
 */
@Configuration
public class ShiroConfig {
    /*随记: 根据三个方法的引用顺序排了序*/

    /*这里CustomRealm类没有加注解注册到spring框架,所以方法new出来返回的,如果类上加了,这里可以省略这个方法
    * 比如给CustomRealm类用Component注解进行注册后, 直接向securityManager(CustomRealm customRealm)方法中传参即可,如下*/
/*    @Bean
    public CustomRealm customRealm() {
        CustomRealm customRealm = new CustomRealm();
        return customRealm;
    }*/

    /*SecurityManger:SecurityManager是Shiro核心,主要协调Shiro内部的各种安全组件,这个我们不需要太关注,只需要知道可以设置自定的Realm*/
    @Bean("securityManager")
    public SecurityManager securityManager(CustomRealm customRealm) {
        DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
        //存入Realm
        defaultSecurityManager.setRealm(customRealm);
        return defaultSecurityManager;
    }

    /*bean名称必须是shiroFilter*/
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        //创建一个shiroFilter实例
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        //配置Manager
        shiroFilter.setSecurityManager(securityManager);

        //自定义过滤规则,可以自定义多个, 通过map传入
        Map<String, Filter> filters = new HashMap<>();
        //第一个参数过滤器, 第二个参数是具体的过滤器实例
        filters.put("oauth2", new TestFilter());
        //将自定义的过滤器加入shiroFilter中
        shiroFilter.setFilters(filters);

        //设置登录路径,设置后登录路径下不走认证,也可以用下面kv的形式写入
        shiroFilter.setLoginUrl("/login");
        //设置权限不足是跳转页面
        shiroFilter.setUnauthorizedUrl("/notRole");
        //上面两个方法不能很全面,所以可以通过LinkedHashMap向过滤器传入过滤配置, 因为存入的规则是有序的
        Map<String, String> filterMap = new LinkedHashMap<>();
        //第一个参数是路径, 第二个参数字典值,标识过滤规则
        //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        //需要注意匹配按照上下顺序来的,  如果/**写在最前面那么,后面的都被覆盖了
        filterMap.put("/sys/login","anon");  //这个的意思就是 /sys/login 可以匿名访问不进行认证
        filterMap.put("/swagger/**", "anon"); //可以用**进行匹配所有子路径
        filterMap.put("/swagger-ui.html", "anon");  //也可以是静态文件
        filterMap.put("/admin/**", "authc");  //admin下所有都必须通过认证才能访问
        filterMap.put("/test/**", "oauth2");  //还可以指定自定义的过滤规则
        filterMap.put("/**", "oauth2");

        //将上面配置的kv存入
        shiroFilter.setFilterChainDefinitionMap(filterMap);

        //返回
        return shiroFilter;
    }
}

filter类

package com.atguigu.gulimall.shiro_demo.demo;

import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

/**
 * @author zhangshaokun
 * @date 2021/3/11
 * 自定义的过滤器
 */
public class TestFilter extends AuthenticatingFilter{
    @Override
    protected AuthenticationToken createToken(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws Exception {
        //获取请求token
        String token = getRequestToken((HttpServletRequest) servletRequest);
        //如果携带没有携带token,则返回null
        if(StringUtils.isEmpty(token)){
            return null;
        }
        //携带了token便返回自定义的token实例
        return new OAuthToken(token);
    }

    @Override
    protected boolean onAccessDenied(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws Exception {
        //获取请求token
        String token = getRequestToken((HttpServletRequest) servletRequest);
        //如果不存在返回401, 这里简化为输出,实际中要给前端返回401及其他错误信息
        if(StringUtils.isEmpty(token)){
            System.out.println("错误401:token为空");
            return false;
        }

        return executeLogin(servletRequest, servletResponse);
    }


    /**
     * 获取请求中的token
     */
    private String getRequestToken(HttpServletRequest httpRequest){
        //从header中获取token
        String token = httpRequest.getHeader("token");

        //如果header中不存在token,则从参数中获取token
        if(StringUtils.isEmpty(token)){
            token = httpRequest.getParameter("token");
        }

        return token;
    }
}

自定义认证时流程

参考链接: https://blog.csdn.net/long270022471/article/details/62423286

这里只是方便自己理解, 那些个文件是干啥的用在了哪…

1.首先根据config中路径匹配到对应的自定义filter

在这里插入图片描述

2.filter中首先进入onAccessDenied方法,判断请求有没有token

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fCE3IqK5-1615532025391)(C:\Users\ZSK\Desktop\学习笔记\img\Shiro.assets\image-20210312101701175.png)]

3.存在token时便进入executeLogin()进行登录步骤 ,其中会调用filter中的重写的createToken方法获取token实例, 调用subject.login(token)进行具体登录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w0gpkEBO-1615532025393)(C:\Users\ZSK\Desktop\学习笔记\img\Shiro.assets\image-20210312101857040.png)]

4.跟随参考链接中的具体流程走回到这一步,用到了我们在Realm中重写的supports方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LJnZdwMm-1615532025393)(C:\Users\ZSK\Desktop\学习笔记\img\Shiro.assets\image-20210312102547686.png)]

5.认证这一步深入便会进入重写的身份认证方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yCWGbDNk-1615532025394)(C:\Users\ZSK\Desktop\学习笔记\img\Shiro.assets\image-20210312102734881.png)]

测试

建一个controller测试

@RestController
@RequestMapping("/sys")
public class testController {
    //模拟登陆接口,不需要经过认证
    @RequestMapping("/login")
    public String login(){
        //实际中肯定要把token返回的, 因为我的验证中token是写死的所以偷懒了
        return new String("登陆成功");
    }

    //模拟需要认证的接口
    @RequestMapping("/query")
    public String querytest(){
        return new String("查询成功");
    }
}

然后用postman调借借口就可以了

http://localhost:10000/sys/login 因为配置的不走认证所以不加token也可以调用
在这里插入图片描述
http://localhost:10000/sys/query 不加token 会输出错误信息,因为我懒,没往前端返所以只能在控制台看
在这里插入图片描述
加token便可以调用成功
在这里插入图片描述

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot 和 Shiro 都是非常流行的Java开发框架。其中,Spring Boot是一个快速开发框架,能够快速地搭建一个Web应用程序;而Shiro是一个强大的安全框架,提供了认证、授权、加密、会话管理等安全相关的功能。 下面是实现Spring Boot和Shiro权限管理的步骤: 1. 引入依赖 在pom.xml文件中引入Spring Boot和Shiro的依赖。 ``` <!-- Spring Boot --> <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.0</version> </dependency> ``` 2. 编写Shiro配置类 编写一个Shiro配置类,用于配置Shiro的安全管理器、Realm、过滤器等。 ``` @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 设置过滤器链 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/logout", "logout"); filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); // 登录页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 认证成功后跳转页面 shiroFilterFactoryBean.setSuccessUrl("/index"); // 未授权页面 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm()); return securityManager; } @Bean public Realm realm() { return new MyRealm(); } } ``` 3. 编写Realm 编写一个Realm类,用于进行认证和授权。 ``` public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); User user = userService.getUserByUsername(username); if (user == null) { throw new UnknownAccountException("用户不存在"); } return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); } // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String username = (String) principalCollection.getPrimaryPrincipal(); User user = userService.getUserByUsername(username); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRoles(user.getRoles()); authorizationInfo.addStringPermissions(user.getPermissions()); return authorizationInfo; } } ``` 4. 编写Controller 编写一个Controller类,用于处理用户登录、登出等请求。 ``` @Controller public class LoginController { @GetMapping("/login") public String login() { return "login"; } @PostMapping("/login") public String doLogin(String username, String password, boolean rememberMe) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); token.setRememberMe(rememberMe); try { subject.login(token); return "redirect:/index"; } catch (AuthenticationException e) { return "login"; } } @GetMapping("/logout") public String logout() { Subject subject = SecurityUtils.getSubject(); subject.logout(); return "redirect:/login"; } @GetMapping("/index") public String index() { return "index"; } @GetMapping("/unauthorized") public String unauthorized() { return "unauthorized"; } } ``` 5. 编写页面 编写登录页面、首页、未授权页面等页面。 ``` <!-- 登录页面 --> <form method="post" action="/login"> <input type="text" name="username" placeholder="用户名" required> <input type="password" name="password" placeholder="密码" required> <div> <input type="checkbox" name="rememberMe" id="rememberMe"> <label for="rememberMe">记住我</label> </div> <button type="submit">登录</button> </form> <!-- 首页 --> <h1>欢迎访问首页</h1> <!-- 未授权页面 --> <h1>您没有访问该页面的权限</h1> ``` 以上就是Spring Boot和Shiro权限管理的实现步骤。通过配置Shiro的安全管理器、Realm、过滤器等,可以实现用户认证和授权。同时,通过在Controller中处理用户登录、登出等请求,可以实现用户的登录和退出功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值