springboot+shiro+jwt实现基于token的无状态授权认证

这里不再讲shiro的原理和鉴权认证流程,网上一大堆,可以自行查阅。我们知道,原本的shiro是基于session来进行认证登录的,但现如今大多数都是前后端分离的,session不利于跨域,以及页面的跳转应该是由前端来控制,后端最好不参与页面的跳转,只返回所需的数据。所以,这里将对shiro禁用session,整合jwt实现无状态的token登录认证。话不多说,我们直接上代码。
最后会附上我的项目地址。

maven的配置

        <!-- redis 相关 dependency start-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- shiro依赖,使用最新的springboot starter的形式 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.9.1</version>
        </dependency>
        <!-- token依赖 -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.19.2</version>
        </dependency>

JWT工具类

@Slf4j
public class JWTUtil {

    private static final long EXPIRE = 60 * 1000L;// token的有效时长
    private static final String SECRET = "jwt+shiro+heZhan";// token的私钥
    private static final String USER_KEY = "userName";

    public static String createBearerToken(String userName){
        return "Bearer " + createToken(userName);
    }

    /**
     * 创建token
     * @param userName 用户名
     * @return 创建的token
     */
    public static String createToken(String userName){
        // token的过期时间
        long current = System.currentTimeMillis();
        Date date = new Date(current + EXPIRE);
        // jwt的header部分
        Map<String, Object> map = new HashMap<>();
        map.put("alg", "HS256");
        map.put("typ", "JWT");

        // 创建token
        String token;
        try {
            token = JWT.create()
                    .withHeader(map) //header部分
                    .withClaim(USER_KEY, userName) //存储用户信息
                    .withClaim("current", current) //当前的时间戳
                    .withExpiresAt(date) //过期时间
                    .withIssuedAt(new Date(current)) //签发时间
                    .sign(Algorithm.HMAC256(SECRET)); //私钥
        } catch (Exception e){
            log.error("为用户{}创建token失败", userName);
            throw new RuntimeException("为用户创建token失败", e);
        }
        return token;
    }

    /**
     * 校验token
     * @param token 传入的token
     * @return 是否校验通过
     */
    public static boolean verifyToken(String token) throws AuthenticationException {
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            verifier.verify(token);
            return true;
        } catch (Exception e){
            log.error("校验token={}失败", token, e);
            throw e;
        }
    }

    /**
     * 从token中获取用户信息
     * @param token 传入的token
     * @return 用户信息
     */
    public static String getUserInfo(String token){
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(USER_KEY).asString();
        } catch (Exception e){
            log.error("从token={}中获取用户信息失败", token, e);
            return null;
        }
    }

    /**
     * 判断是否过期
     */
    public static boolean isExpire(String token) {
        DecodedJWT jwt = JWT.decode(token);
        return jwt.getExpiresAt().getTime() < System.currentTimeMillis();
    }

}

密码加密工具类

因为用户输入账号密码后,我们在首页第一次登录需要匹配账号密码,但这里的密码是明文的,很不安全,所以这里我们对密码进行加密,shiro提供了加密的方法,我们这里直接调用。

public class EncryptionUtil {

    /**
     * 对用户输入的密码进行加密,并返回16进制的字符串
     * @param password 输入的密码
     * @param salt 加密盐
     * @return
     */
    public static String encryption(String password, String salt){
        // 加盐加密,目的是为了让相同的密码通过不同的盐hash散列后的值不同
        ByteSource byteSource = ByteSource.Util.bytes(salt);
        SimpleHash result = new SimpleHash("SHA-1", password, byteSource, 2);
        return result.toHex();
    }
}

上面的SimpleHash类在new的时候传入了4个参数:

  1. 表明加密的算法,这里使用了"SHA-1"的加密算法;
  2. 要加密的数据,这里传入了用户的密码;
  3. 加密盐,这里使用账号作为加密盐,确保万一两个人使用了同样的密码,但是账号不同,加密后的数据也会不同;
  4. 加密次数。

最后返回16进制的加密后的字符串,因为在shiro中的密码匹配器中,默认返回的也是16进制的字符串。

首页使用账号密码登录

这里我们先模拟首页登录,一般首页都是输入账号密码来登录,然后后台这里,我们先去匹配用户的账号密码,匹配成功后,下发token,之后的请求,都是携带下发的token去请求。

自定义Realm类

@Component
@DependsOn("myHashedCredentialsMatcher")
public class MyRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    /**
     * 构造器中配置登录校验器
     */
    public MyRealm(MyHashedCredentialsMatcher myHashedCredentialsMatcher) {
        super();
        myHashedCredentialsMatcher.setHashAlgorithmName("SHA-1");// 加密算法的名称
        myHashedCredentialsMatcher.setHashIterations(2);// 加密的次数
        myHashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);// 是否储存为16进制
        this.setCredentialsMatcher(myHashedCredentialsMatcher);
    }

    /**
     * 授权,权限校验
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        // 这里获取到了登录信息后,可以根据用户从数据库里获取该用户所拥有的权限
        // 这里只作为演示,所以就写死了几个权限存放
        Set<String> permissions = new HashSet<>();
        permissions.add("user:show");
        permissions.add("user:admin");
        permissions.add("user:add");
        info.setStringPermissions(permissions);
        return info;
    }

    /**
     * 认证,登录认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String userName = usernamePasswordToken.getUsername();
        String password = new String(usernamePasswordToken.getPassword());

        User user = userService.getUserByName(userName);
        if (user == null || StringUtils.isBlank(userName) || StringUtils.isBlank(password)){
            throw new AuthenticationException();
        }
//        if (user.getState() == 1){
//            throw new ExcessiveAttemptsException();
//        }
        // 这里将user作为主体存放起来,后面要用的话,可以 (User) SecurityUtils.getSubject().getPrincipal(); 这样获取
        return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getName()), getName());
    }

    /**
     * <p>设置此Realm处理哪种类型的登录,这里标明处理UsernamePasswordToken类型的登录,也就是账号密码形式的登录。</p>
     * <p>因为shiro的机制是根据subject.login(token)这个登录方法中的token类型来分配Realm</p>
     * @return
     */
    @Override
    public Class getAuthenticationTokenClass() {
        return UsernamePasswordToken.class;
    }
}

配置自定义的密码匹配器

@Slf4j
@Component
public class MyHashedCredentialsMatcher extends HashedCredentialsMatcher {

    @Resource
    private RedisUtil redisUtil;// 一个redis工具类

    @Resource
    private UserService userService;//从数据库里获取用户信息的service

    public static final String KEY_PREFIX = "shiro:cache:retryLimit:";

    public static final Integer MAX = 5;// 最大登录次数

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        // 获取用户名
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String userName = usernamePasswordToken.getUsername();
        String key = KEY_PREFIX + userName;
        // 获取用户登录失败次数
        AtomicInteger atomicInteger = (AtomicInteger) redisUtil.get(key);
        if (atomicInteger == null){
            atomicInteger = new AtomicInteger(0);
        }
        User user = userService.getUserByName(userName);
        if (atomicInteger.incrementAndGet() > MAX){
            // 如果用户登录失败次数大于5次,抛出锁定用户异常,并修改数据库用户状态字段
            if (user != null && user.getState() != 1){
                user.setState(1);// 设置为锁定状态
                userService.updateById(user);
            }
            log.info("锁定用户"+ userName);
            throw new ExcessiveAttemptsException();
        }
        // 判断用户的账号和密码是否正确
        boolean matches = super.doCredentialsMatch(token, info);
        if (matches){
            // 如果匹配上了
            redisUtil.delete(key);
            // 将用户的状态改为0
            if (user.getState() != 0){
                userService.updateUserState(user.getId(), 0);
            }
        } else {
            redisUtil.set(key, atomicInteger, 300);
        }
        return matches;
    }
}

到这里,我们关于首页使用账号密码登录认证的主体代码已经写完,接下来还需要写一个shiroConfig的配置类去装配,这个先等我们写完接下来的token认证的代码再来写config类。

首页登录后的其他请求,基于token认证

自定义Realm类

@Component
@DependsOn("myJWTCredentialsMatcher")
public class JWTRealm extends AuthorizingRealm {

    public JWTRealm(MyJWTCredentialsMatcher myJWTCredentialsMatcher) {
        super(myJWTCredentialsMatcher);
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        // 这里获取到了登录信息后,可以根据用户从数据库里获取该用户所拥有的权限
        // 这里只作为演示,所以就写死了几个权限存放
        Set<String> permissions = new HashSet<>();
        permissions.add("user:show");
        permissions.add("user:admin");
        permissions.add("user:add");
        info.setStringPermissions(permissions);
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        BearerToken bearerToken = (BearerToken) token;
        String tokenString = bearerToken.getToken();
        String userName = JWTUtil.getUserInfo(tokenString);
        User user = new User();
        user.setName(userName);
        return new SimpleAuthenticationInfo(user, tokenString, getName());
    }

    @Override
    public Class getAuthenticationTokenClass() {
        return BearerToken.class;
    }
}

自定义JWT过滤器

@Slf4j
public class JWTFilter extends BearerHttpAuthenticationFilter {

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        boolean res = false;
        HttpServletRequest req = WebUtils.toHttp(request);
        // 先判断是否传入了token
        if (!isLoginAttempt(request, response)){
            req.setAttribute("exception", new NotLoginException("未登录!"));
            req.getRequestDispatcher("/api/loginError").forward(request, response);
            return false;
        }
        // 再看是否过期
        BearerToken token = (BearerToken) createToken(request, response);
        if (JWTUtil.isExpire(token.getToken())){
            // 刷新token
            if (!refreshToken(request, response)){
                req.getRequestDispatcher("/api/loginExpire").forward(request, response);
                return false;
            }
            return true;
        }
        try {
            /*
            这里最终会调用subject.login(token)去处理认证,
            这里因为继承了BearerHttpAuthenticationFilter,
            所以这里会自动包装成一个BearerToken作为参数代入subject.login(token)中,
            怎么包装的呢?原来它会从请求头里获取一个"Authorization"字段的值,拿到这个值去进行包装
             */
            res = super.onAccessDenied(request, response);
        } catch (Exception e){
            Throwable cause = e.getCause();
            if (cause instanceof TokenExpiredException){
                refreshToken(request, response);
            } else {
                throw e;
            }
        }
        return res;
    }

    /**
     * 刷新token
     * @param request
     * @param response
     * @return
     */
    private boolean refreshToken(ServletRequest request, ServletResponse response){
        log.info("刷新token...");
        HttpServletRequest req= (HttpServletRequest) request;
        String tokenHeader = req.getHeader(AUTHORIZATION_HEADER);
        String[] tokens = tokenHeader.split(" ");
        String token = tokens[1];
        String userName = JWTUtil.getUserInfo(token);
        String newToken = JWTUtil.createToken(userName);
        BearerToken bearerToken = new BearerToken(newToken);
        try {
            getSubject(request, response).login(bearerToken);
            HttpServletResponse res = (HttpServletResponse) response;
            res.setHeader("Access-Control-Expose-Headers", "Authorization");
            res.setHeader("Authorization", "Bearer " + newToken);
            return true;
        } catch (Exception e){
            log.error("token刷新失败", e);
            return false;
        }
    }
}

这里,我们本可以直接使用shiro自带的BearerHttpAuthenticationFilter来校验token,但这里有个问题,就是如果token过期后,它不会抛出异常,而是返回一个false,这里和token校验的其他错误一样,不会抛异常,只会返回false,所以我们需要写一个自定义的filter来重写它的认证方法,先判断token是否过期,再刷新下token。这里只是模拟,所以token过期了就直接刷新token,在企业里应用的话,还需要一个refreshToken,这个是可以在生成token的同时生成,并写在缓存里,刷新token的时候去校验下refreshToken。

自定义token匹配器

@Slf4j
@Component
public class MyJWTCredentialsMatcher implements CredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws RuntimeException{
        BearerToken bearerToken = (BearerToken) token;
        String tokenString = bearerToken.getToken();
        return JWTUtil.verifyToken(tokenString);
    }
}

多Realm下的配置

因为这里是多个Realm类,原本在这里可以由Realm来自动注册装配的,然后由ModularRealmAuthenticator来自动管理的,但是这样的话,realm类去做认证鉴权时抛出的异常,在这里就会被捕获,而且不会抛出,如下图:
在这里插入图片描述
所以,我们需要自定义一个类,来继承ModularRealmAuthenticator并重写此方法:

@Slf4j
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator {

    /**
     * 重写,以便用realm进行登录认证时可以成功抛出异常
     * @param realms
     * @param token
     * @return
     */
    @Override
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) throws AuthenticationException {
        AuthenticationStrategy strategy = getAuthenticationStrategy();

        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

        if (log.isTraceEnabled()) {
            log.trace("Iterating through {} realms for PAM authentication", realms.size());
        }

        for (Realm realm : realms) {

            try {
                aggregate = strategy.beforeAttempt(realm, token, aggregate);
            } catch (ShortCircuitIterationException shortCircuitSignal) {
                // Break from continuing with subsequnet realms on receiving
                // short circuit signal from strategy
                break;
            }

            if (realm.supports(token)) {

                log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);

                AuthenticationInfo info = null;
                Throwable t = null;
                // 下面这行取消try catch,直接拿出来,有异常则直接抛出,其它的用原代码
                info = realm.getAuthenticationInfo(token);
                aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);

            } else {
                log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
            }
        }

        aggregate = strategy.afterAllAttempts(token, aggregate);

        return aggregate;
    }
}

这个类,会在下面要写的config中装配一下。

配置shiroConfig

配置shiro禁用session

public class MySubjectFactory extends DefaultWebSubjectFactory {

    /**
     * 重写此方法,禁止Subject创建session
     * @param context
     * @return
     */
    @Override
    public Subject createSubject(SubjectContext context) {
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }
}
public class MySubjectDAO extends DefaultSubjectDAO {

    @Override
    protected boolean isSessionStorageEnabled(Subject subject) {
        return false;
    }
}

总共是两个地方都要继承并重写,才能禁用session。

配置跨域的filter

@Slf4j
public class CorsFilter extends PathMatchingFilter {
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setHeader("Access-control-Allow-Origin",httpRequest.getHeader("Origin"));
        httpResponse.setHeader("Access-control-Allow-Methods","GET,POST,OPTIONS,PUT,DELETE");
        httpResponse.setHeader("Access-control-Allow-Headers",httpRequest.getHeader("Access-Control-Request-Headers"));
        //防止乱码,适用于传输JSON数据
        httpResponse.setHeader("Content-Type","application/json;charset=UTF-8");
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            log.debug("收到一个OPTIONS请求--"+httpRequest.getRequestURI());
            httpResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
}

shiroConfig

@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/api/login");
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("cors", new CorsFilter());
        filterMap.put("jwt", new JWTFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        /*
        因为这里配置的路径和拦截规则,是需要按照顺序的,所以使用LinkedHashMap而不是HashMap
         */
        Map<String, String> map = new LinkedHashMap<>();
        // authc:所有url都必须认证通过才可以访问,anon:所有url都可以匿名访问
        map.put("/api/*", "anon");
//        map.put("/**", "authc");
        /*
        使用BearerHttpAuthenticationFilter过滤器来拦截,并获取请求头里的Authorization字段,
        并将其所携带的jwt token内容包装成一个BearerToken对象,并调用login方法进入realm进行身份验证。
         */
        map.put("/**", "jwt");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        // 下面注释的那个,全局配置的禁用session的不管用,需要再覆盖两个Bean注入,注入的方法往下看
//        shiroFilterFactoryBean.setGlobalFilters(Collections.singletonList("noSessionCreation"));//关键:全局配置NoSessionCreationFilter,把整个项目切换成无状态服务。
        return shiroFilterFactoryBean;
    }

    /*
    下面两个Bean(subjectDAO和subjectFactory),作用是关闭Subject的session
     */

    @Bean
    public SubjectDAO subjectDAO(){
        return new MySubjectDAO();
    }

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

    @Bean
    public Authorizer authorizer(){
        return new ModularRealmAuthorizer();
    }

    /**
     * 设置多个realm处理登录时可以抛出异常
     * @return
     */
    @Bean
    public Authenticator authenticator(){
        return new MyModularRealmAuthenticator();
    }

}

测试

首页登录controller

@RestController
@Slf4j
@RequestMapping("/api")
public class LoginController {

    @GetMapping("/login")
    public String frontPage(){
        return "未登录,跳首页去登录";
    }

    /**
     * 使用账号密码来登录,通过登录校验后,返回token
     * @param loginInfo 登录信息
     * @return
     */
    @PostMapping("/login")
    public String login(@RequestBody LoginInfo loginInfo, HttpServletResponse response) {
        // 创建一个subject,是shiro的登录用户主体
        Subject subject = SecurityUtils.getSubject();
        // 认证提交前准备token
        UsernamePasswordToken token = new UsernamePasswordToken();
        token.setUsername(loginInfo.getUserName());
        token.setPassword(loginInfo.getPassword().toCharArray());
        // 执行登录
        try {
            /*
            这里就会调用Realm去处理登录校验之类的事情,至于用哪个Realm,就看这里传入的token是哪个类的token,
            然后由接管不同类型token的Realm去处理
             */
            subject.login(token);
            User user = (User) subject.getPrincipal();
            String tokenString = JWTUtil.createBearerToken(user.getName());
            response.setHeader(ConstantEnum.AUTHORIZATION.getValue(), tokenString);
        } catch (LockedAccountException e){
            subject.logout();
            return "账号已被锁定,请联系管理员!";
        } catch (UnknownAccountException e){
            subject.logout();
            return "未知账号!";
        } catch (ExcessiveAttemptsException e){
            subject.logout();
            return "账号或密码错误次数过多!请5分钟后再登录!";
        } catch (IncorrectCredentialsException e){
            subject.logout();
            return "密码不正确!";
        } catch (AuthenticationException e){
            subject.logout();
            return "账号或密码不正确!";
        }
        if (subject.isAuthenticated()){
            return "登录成功";
        } else {
            subject.logout();
            return "登录失败";
        }
    }

    @PostMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "退出登录!";
    }

    @GetMapping("/loginError")
    public String loginError(HttpServletResponse response) {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        return "未登录!";
    }

    @GetMapping("/loginExpire")
    public String loginExpire(){
        return "token已过期,请重新登录!";
    }
}

权限鉴权controller

@RestController
@Slf4j
@RequestMapping("/permission")
public class PermissionController {

    /**
     * 这个接口模拟有权限后成功调用
     * @return
     */
    @GetMapping("/show")
    @RequiresPermissions("user:add")
    public String showPermission(){
        return "有权限看到此信息...";
    }

    /**
     * 这个接口模拟没有权限
     * @return
     */
    @GetMapping("/showUnable")
    @RequiresPermissions("user:update")
    public String showPermissionUnable(){
        return "有权限看到此信息...";
    }
}

项目地址

项目地址:代码链接

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: springboot+shiro+jwt 是一种常见的后端技术组合,其中 springboot 是一个基于 Spring 框架的快速开发框架,shiro 是一个安全框架,用于身份验证、授权和加密等功能,jwt 是一种基于 token 的身份验证机制,可以用于前后端分离的应用中。这种技术组合可以帮助开发者快速搭建安全可靠的后端服务。 ### 回答2: Springboot是一个开源的Java开发框架,是基于Spring框架的远程服务器控制技术方案,是现代化的全栈框架,集成丰富,提供了大量的开箱即用的组件,可以大大简化开发人员的开发工作。 Shiro是一个强大的轻量级安全框架,支持用户身份识别、密码加密、权限控制、会话管理等一系列安全功能,被广泛应用于JavaWeb开发中。 JWT(JSON Web Token)是一种开放标准(RFC 7519),定义了一种简洁的、自包含的方式,用于通信双方之间以JSON对象的形式安全地传递信息。JWT可以用于状态管理和用户身份认证等场景。 在使用SpringBoot开发Web应用过程中,ShiroJWT可以同时用于用户身份认证和权限控制。Shiro提供了一系列的身份识别、密码加密、权限控制、会话管理等功能,而JWT则可以实现状态的身份认证。使用ShiroJWT,可以有效地保护Web应用的安全,避免被恶意攻击者利用。 具体而言,使用ShiroJWT可以将用户认证的主要逻辑统一在一起,实现更加优雅的认证授权过程。同时,这样的组合也可以避免一些常见的安全漏洞,比如会话劫持、XSS攻击、CSRF等。 在实际开发中,使用SpringBoot Shiro JWT可以方便地进行二次开发,进一步优化开发成本和提高开发效率。同时,使用这个组合可以让开发者更好地专注于业务逻辑的开发,而无需关注安全问题,从而提高开发质量和开发人员的工作效率。 ### 回答3: Spring Boot是一种基于Spring框架的快速开发微服务的工具,能够使开发者可以快速构建基于Spring的应用程序。而Shiro是一个强大易用的Java安全框架,可用于身份验证、权限控制、加密等。JWT(JSON Web Token)是一种基于JSON的安全令牌,可用于在客户端和服务器之间传递信息。 在使用Spring Boot开发Web应用程序时,通常需要进行用户身份验证和访问控制,这时候就可以使用Shiro实现这些功能。同时,由于Web应用程序需要跨域访问,因此使用JWT可以方便地实现身份验证和授权的管理。 在使用Spring BootShiro时,可以使用JWT作为身份验证和授权的管理工具。此时,需要进行以下几个步骤: 1.添加ShiroJWT的依赖 在Spring Boot项目中,可以通过Maven或Gradle等工具添加ShiroJWT的依赖。例如,可以添加以下依赖: <!-- Shiro依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.7.0</version> </dependency> <!-- JWT依赖 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> 2.配置ShiroSpring Boot项目中,可以通过在application.properties或application.yml文件中进行Shiro的配置。例如,可以配置以下内容: # Shiro配置 shiro: user: loginUrl: /login # 登录页面URL jwt: secret: my_secret # JWT加密密钥 expiration: 1800000 # JWT过期时间,单位为毫秒,默认30分钟 3.创建Shiro的Realm 在Shiro中,Realm是用于从应用程序中获取安全数据(如用户、角色和权限)的组件。因此,需要创建Shiro的Realm,用于管理用户的认证授权。 例如,可以创建一个自定义的Realm,其中包括从数据库中获取用户和角色的方法: public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; /** * 认证,验证用户身份 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); User user = userService.findByUsername(username); if (user == null) { throw new UnknownAccountException("用户名或密码错误"); } String password = user.getPassword(); return new SimpleAuthenticationInfo(user.getUsername(), password, getName()); } /** * 授权,验证用户的访问权限 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); User user = userService.findByUsername(username); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); Set<String> roles = user.getRoles(); authorizationInfo.setRoles(roles); Set<String> permissions = user.getPermissions(); authorizationInfo.setStringPermissions(permissions); return authorizationInfo; } } 4.使用JWT进行身份验证和授权管理 在Spring Boot应用程序中,使用ShiroJWT来进行身份验证和授权管理的流程大致如下: 4.1 前端用户进行登录操作,将用户名和密码发送到后台服务。 4.2 后台服务进行身份验证,将用户身份信息生成JWT并返回给前端。 4.3 前端保存JWT,并在后续的请求中将其发送到后台服务。 4.4 后台服务验证JWT的有效性,并根据用户的角色和权限信息进行访问控制。 综上所述,Spring BootShiroJWT可以很好地配合使用,实现Web应用程序的身份验证和授权管理。这种组合方案可以提高开发效率和系统安全性,同时也适用于微服务架构中对身份验证和授权的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值