SpringBoot整合Shiro+Jwt实现登录认证和授权

shiro

简介

  1. Subject:主体
    1. 代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;
  2. SecurityManager:安全管理器
    1. 即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制。
  3. Realms:域
    1. Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

登录认证代码实现

pom

<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starterartifactId>
    dependency>

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>2.1.2version>
    dependency>
    <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plus-boot-starterartifactId>
        <version>3.3.2version>
    dependency>
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <optional>trueoptional>
        <version>1.18.12version>
    dependency>
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <scope>runtimescope>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-jdbcartifactId>
    dependency>
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druid-spring-boot-starterartifactId>
        <version>1.1.20version>
    dependency>

    
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-coreartifactId>
        <version>${shiro-version}version>
    dependency>
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-springartifactId>
        <version>${shiro-version}version>
    dependency>
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-webartifactId>
        <version>${shiro-version}version>
    dependency>


    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintagegroupId>
                <artifactId>junit-vintage-engineartifactId>
            exclusion>
        exclusions>
    dependency>
dependencies>

实体

用户实体

@Data
@TableName("sys_user")
public class SysUser {

    private int id;
    private String username;
    private String password;
    private int status;
    private int isDelete;

}

用户数据库

id username password status is_delete
1 admin 123 0 0
2 aaa 82c1a1ef7dd57d095f3d221e51bd6b16 0 0

自定义realm

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private SysUserService userService;

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

    /**
     * 登录认证
     * @param authenticationToken 封装的token(UsernamePasswordToken)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        if (authenticationToken.getPrincipal() == null) {
            throw new AuthenticationException("token不合法");
        }
        String username = authenticationToken.getPrincipal().toString();
        log.info("用户名:{}", username);
        SysUser one = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
        if (one == null) {
            // 没有该用户名
            log.error("没有该用户名: {}", username);
            throw new AuthenticationException("用户不存在");
        }
        // 用户状态判断
        if (one.getStatus() == 1) {
            throw new AccountException("用户被禁用");
        }
        if (one.getIsDelete() == 1) {
            throw new AccountException("用户被删除");
        }
        // 其他业务判断
        // 判断通过后,将数据库中查询出来的user封装为info
        return new SimpleAuthenticationInfo(
                one.getUsername(),// 这个参数是什么,在后续的subject.getPrincipal就是什么,也可以设置用户实体
                one.getPassword(),
//                ByteSource.Util.bytes(one.getUsername()),// 密码加密的"盐值",可以是username、id等
                getName()
        );
    }
}

shiro配置类

public class ShiroConfig {

    @Bean("credentialsMatcher")
    public HashedCredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 密码加密算法
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        // 加密次数
        hashedCredentialsMatcher.setHashIterations(512);
        return hashedCredentialsMatcher;
    }

    /**
     * 自定义realm
     */
    @Bean("myRealm")
    public MyRealm myRealm(@Qualifier("credentialsMatcher") HashedCredentialsMatcher credentialMatcher) {
        MyRealm myRealm = new MyRealm();
//        myRealm.setCredentialsMatcher(credentialMatcher);
        return myRealm;
    }

    @Bean("securityManager")
    public SecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm);
        /*
         * 关闭shiro自带的session,详情见文档
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);
        return manager;
    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
        filter.setSecurityManager(securityManager);
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 路径匹配的顺序就是put进去的顺序(最先匹配原则)
        // login接口不需要认证
        filterChainDefinitionMap.put("/auth/login", "anon");
        // getInfo需要认证
        filterChainDefinitionMap.put("/auth/getInfo", "authc");
		filterChainDefinitionMap.put("/**", "authc");
        
        filter.setLoginUrl("/auth/login");
        filter.setSuccessUrl("/auth/getInfo");
        filter.setUnauthorizedUrl("/auth/error");
        filter.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return filter;
    }

    /**注册shiro的Filter 拦截请求*/
    @Bean
    public FilterRegistrationBean<Filter> filterRegistrationBean(SecurityManager securityManager) throws Exception {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter((Filter) Objects.requireNonNull(this.shiroFilterFactoryBean(securityManager).getObject()));
        filterRegistrationBean.addInitParameter("targetFilterLifecycle","true");
        //bean注入开启异步方式
        filterRegistrationBean.setAsyncSupported(true);
        filterRegistrationBean.setEnabled(true);
        filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST);
        return filterRegistrationBean;
    }


    /**
     * shiro声明周期
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    // 以下配置开启shiro注解(@RequiresPermissions)

    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        // https://zhuanlan.zhihu.com/p/29161098
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 启用shiro注解
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

登录业务

public class AuthServiceImpl implements AuthService {

    @Override
    public String login(SysUser sysUser) {
        // 非空判断
        if (sysUser == null) {
            return null;
        }
        if (sysUser.getUsername() == null || sysUser.getPassword() == null) {
            return null;
        }
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                sysUser.getUsername(),
                sysUser.getPassword()
        );
        try {
            subject.login(usernamePasswordToken);
        } catch (AuthenticationException e) {
            log.error("登录失败");
            throw new AuthenticationException("登录失败");
        }
        return "登录成功";
    }
}

接口

@RestController
@RequestMapping("auth")
public class AuthController {
    @Autowired
    private AuthService authService;

    @PostMapping("login")
    public String login(@RequestBody SysUser sysUser) {
        return authService.login(sysUser);
    }

    @GetMapping("getInfo")
    public String getInfo() {
        return SecurityUtils.getSubject().getPrincipal().toString();
    }

    @RequestMapping("error")
    public String error() {
        return "fail";
    }
}

测试

SpringBoot整合Shiro+Jwt实现登录认证和授权_第1张图片

当直接访问auth/getInfo时,可以看到,无法访问该接口,并且会自动跳转到登录接口(auth/login)。

SpringBoot整合Shiro+Jwt实现登录认证和授权_第2张图片

使用用户名和密码登录

然后再访问auth/getInfo接口

现在可以正确获取到信息,可以看到获取到的SecurityUtils.getSubject().getPrincipal(),就是在reamlm中返回的info设置的principal参数。

:如果要使用加密,则要在shiro配置类里,给自定义的realm设置密码匹配器

@Bean("credentialsMatcher")
public HashedCredentialsMatcher credentialsMatcher() {
    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    // 密码加密算法
    hashedCredentialsMatcher.setHashAlgorithmName("MD5");
    // 加密次数
    hashedCredentialsMatcher.setHashIterations(512);
    return hashedCredentialsMatcher;
}

/**
     * 自定义realm
     */
@Bean("myRealm")
public MyRealm myRealm(@Qualifier("credentialsMatcher") HashedCredentialsMatcher credentialMatcher) {
    MyRealm myRealm = new MyRealm();
    myRealm.setCredentialsMatcher(credentialMatcher);
    return myRealm;
}

自定义realme里面最后返回的info需要带上加密的“盐值”

return new SimpleAuthenticationInfo(
    one.getUsername(),
    one.getPassword(),
    ByteSource.Util.bytes(one.getUsername()),
    getName()
);

总结

登录认证的流程:

  1. 将前端传来的用户名和密码封装成UsernamePasswordToken
  2. 调用Subject的login(UsernamePasswordToken)方法,实际上是调用的SecurityManager的login方法
  3. 进入自定义realme方法
  4. 最终由定义的密码匹配器进行密码匹配

SimpleCredentialsMatcher源码:默认使用该匹配器,即不加密

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    Object tokenCredentials = this.getCredentials(token);
    Object accountCredentials = this.getCredentials(info);
    return this.equals(tokenCredentials, accountCredentials);
}
// token:前端封装的用户名密码token
// info:realme中返回的info

实际就是把数据库中的密码和前端输入的密码进行对比

HashedCredentialsMatcher源码:加密的密码匹配器

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    Object tokenHashedCredentials = this.hashProvidedCredentials(token, info);
    Object accountCredentials = this.getCredentials(info);
    return this.equals(tokenHashedCredentials, accountCredentials);
}

protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
    Object salt = null;
    if (info instanceof SaltedAuthenticationInfo) {
        salt = ((SaltedAuthenticationInfo)info).getCredentialsSalt();
    } else if (this.isHashSalted()) {
        salt = this.getSalt(token);
    }

    return this.hashProvidedCredentials(token.getCredentials(), salt, this.getHashIterations());
}

// 在这里实现的加密
protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) {
    String hashAlgorithmName = this.assertHashAlgorithmName();
    return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
}

把前端输入的明文密码,用设置的盐值和加密次数进行加密后,再与数据库中的密文密码进行对比。

授权代码实现

授权就需要实现自定义realme中的doGetAuthorizationInfo方法

数据库

sys_role

id name code staus Is_delete
1 管理员 admin 0 0
2 作家 writer 0 0

sys_permission

id name code url status Is_delete
1 用户查看 user:view /user/** 0 0
2 用户增删改 user:edit /user/** 0 0
3 文章查看 article:view /article/** 0 0
4 文章增删改 article:edit /article/** 0 0

另外还有用户角色关联表角色权限关联表

我这里测试的数据是:

admin用户是管理员和作家,aaa用户是作家。

管理员拥有所有权限,作家拥有文章相关的权限。

修改自定义realme

/**
     * 授权
     */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    String username = principalCollection.getPrimaryPrincipal().toString();
    SysUser sysUser = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
    // 去数据库中查询该用户的角色和权限
    List<SysRole> roles = roleService.getRoles(sysUser.getId());
    List<SysPermission> permissions = permissionService.getPermissions(sysUser.getId());

    Set<String> permissionSet = new HashSet<>();
    Set<String> roleSet = new HashSet<>();

    permissions.forEach(item -> permissionSet.add(item.getCode()));
    roles.forEach(item -> roleSet.add(item.getCode()));

    authorizationInfo.addRoles(roleSet);
    authorizationInfo.addStringPermissions(permissionSet);
    return authorizationInfo;
}

当遇到需要鉴权的时候,会走doGetAuthorizationInfo方法

测试

接口

@RequiresPermissions("user:view")
@GetMapping("userView")
public String userView() {
    return "用户查看";
}

@RequiresPermissions("article:view")
@GetMapping("articleView")
public String articleView() {
    return "文章查看";
}

@RequiresRoles("admin")
@GetMapping("admin")
public String admin() {
    return "管理员";
}

登录用户是aaa的时候,只能访问articleView接口,其他接口均无相应的权限。

SpringBoot整合Shiro+Jwt实现登录认证和授权_第3张图片

访问articleView接口,可以成功访问

当访问其他两个接口时,控制台报错无权调用此方法

org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method

整合JWT

准备工作

pom

<dependency>
    <groupId>com.auth0groupId>
    <artifactId>java-jwtartifactId>
    <version>3.10.3version>
dependency>

jwt工具类

创建、解析token

@Slf4j
@Component
public class JwtUtil {

    private static final String secret = "secret";

    /**
     * 创建token
     */
    public static String createToken(String username, Long time) throws UnsupportedEncodingException {
        long expiration = System.currentTimeMillis() + time;
        Date expireDate = new Date(expiration);
        String token = JWT.create()
                .withClaim("sys_username", username)
                .withExpiresAt(expireDate)
                .sign(Algorithm.HMAC256(secret));
        log.info("用户:{} =====> token:{}", username, token);
        return token;
    }

    /**
     * 校验token是否正确
     */
    public static boolean verify(String token, String username) throws UnsupportedEncodingException, TokenExpiredException {
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTVerifier verifier = JWT.require(algorithm)
                .withClaim("sys_username", username)
                .build();
        verifier.verify(token);
        return true;
    }

    /**
     * 解析token,获取用户名
     */
    public static String getUsername(String token) {
        DecodedJWT decode = JWT.decode(token);
        return decode.getClaim("sys_username").asString();
    }
}

自定义token

用自定义的token取代shiro中的token,例如前面的UsernamePasswordToken

public class JwtToken implements AuthenticationToken {

    private String token;

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

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

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

自定义密码匹配器

public class JwtCredentialsMatcher extends HashedCredentialsMatcher {
	/**
     * @param info realme中返回的是username,所以getPrincipals()获取的是用户名
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo info) {
        JwtToken jwtToken = (JwtToken) authenticationToken;
        String token = jwtToken.getCredentials().toString();
        try {
            return JwtUtil.verify(token, info.getPrincipals().toString());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("token解析失败");
        } catch (TokenExpiredException e) {
            throw new RuntimeException("token过期");
        }
    }
}

自定义过滤器

public class JwtFilter extends AccessControlFilter {

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 是否允许访问
     * isAccessAllowed返回false后,去执行onAccessDenied方法
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnsupportedEncodingException {
        Subject subject = SecurityUtils.getSubject();
        String token = HttpUtil.getToken((HttpServletRequest) request);
        if (StringUtils.isNotBlank(token)) {
            JwtToken jwtToken = new JwtToken(token);
            try {
                subject.login(jwtToken);
                return true;// 登录成功
            } catch (Exception e) {
                // 登录失败
                throw new RuntimeException("登录失败");
            }
        } else {
            // 没有token
            return false;
        }
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        String token = HttpUtil.getToken((HttpServletRequest) servletRequest);
        if (StringUtils.isNotBlank(token)) {
            String username = JwtUtil.getUsername(token);
            if (JwtUtil.verify(token, username)) {
                // 没有权限

            }
        } else {
            // 没有token

        }
        return false;
    }
}

修改shiro配置类

@Bean("jwtFilter")
public JwtFilter jwtFilter() {
    return new JwtFilter();
}

@Bean("credentialsMatcher")
public JwtCredentialsMatcher credentialsMatcher() {
    return new JwtCredentialsMatcher();
}

@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
    ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
    filter.setSecurityManager(securityManager);

    Map<String, Filter> filterMap = new LinkedHashMap<>();
    filterMap.put("jwt", new JwtFilter());
    filter.setFilters(filterMap);

    LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    // login接口不需要认证
    filterChainDefinitionMap.put("/auth/login", "anon");
    filterChainDefinitionMap.put("/**", "jwt");
    filter.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return filter;
}

加入自定义的过滤器和密码匹配器,所有接口都需要执行自定义过滤器。

修改自定义realme

@Slf4j
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private SysUserService userService;
    @Autowired
    private SysRoleService roleService;
    @Autowired
    private SysPermissionService permissionService;

    // 不写该方法,会报错不支持自定义的token
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        String username = principalCollection.getPrimaryPrincipal().toString();
        SysUser sysUser = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
        // 去数据库中查询该用户的角色和权限
        List<SysRole> roles = roleService.getRoles(sysUser.getId());
        List<SysPermission> permissions = permissionService.getPermissions(sysUser.getId());

        Set<String> permissionSet = new HashSet<>();
        Set<String> roleSet = new HashSet<>();

        permissions.forEach(item -> permissionSet.add(item.getCode()));
        roles.forEach(item -> roleSet.add(item.getCode()));

        authorizationInfo.addRoles(roleSet);
        authorizationInfo.addStringPermissions(permissionSet);
        return authorizationInfo;
    }

    /**
     * 登录认证
     * @param authenticationToken 自定义的token
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // authenticationToken就是JwtToken
        if (authenticationToken.getPrincipal() == null) {
            throw new AuthenticationException("token不合法");
        }
        String token = authenticationToken.getPrincipal().toString();
        String username = JwtUtil.getUsername(token);
        SysUser one = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
        if (one == null) {
            // 没有该用户名
            log.error("没有该用户名: {}", username);
            throw new AuthenticationException("用户不存在");
        }
        // 用户状态判断
        if (one.getStatus() == 1) {
            throw new AccountException("用户被禁用");
        }
        if (one.getIsDelete() == 1) {
            throw new AccountException("用户被删除");
        }
        // 其他业务判断
        // 判断通过后,将数据库中查询出来的user封装为info
        return new SimpleAuthenticationInfo(
                one.getUsername(),// 这个参数是什么,在后续的subject.getPrincipal就是什么
                one.getPassword(),
                ByteSource.Util.bytes(one.getUsername()),// 密码加密的"盐值",可以是username、id等
                getName()
        );
    }
}

修改登录代码

@Override
public String login(SysUser sysUser) {
    // 非空判断
    if (sysUser == null) {
        return null;
    }
    if (sysUser.getUsername() == null || sysUser.getPassword() == null) {
        return null;
    }
    try {
        String token = JwtUtil.createToken(sysUser.getUsername(), 1440000L);
        Subject subject = SecurityUtils.getSubject();
        JwtToken jwtToken = new JwtToken(token);
        subject.login(jwtToken);
        return token;
    } catch (AuthenticationException e) {
        log.error("登录失败");
        throw new AuthenticationException("登录失败");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return "登录失败";
}

测试

SpringBoot整合Shiro+Jwt实现登录认证和授权_第4张图片

登录成功后,会返回token。

SpringBoot整合Shiro+Jwt实现登录认证和授权_第5张图片

携带token调用接口,成功返回数据

SpringBoot整合Shiro+Jwt实现登录认证和授权_第6张图片

访问不具备权限的接口

控制台报错:org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method: public java.lang.String com.shiro.controller.AuthController.userView()

总结

到此,shiro部分和shiro整合jwt部分完成。这只是一个例子,代码中还有很多需要完善的地方,比如:返回结果的封装、对异常的处理等。

另外一般在系统中,token过期刷新也是必不可少的。

你可能感兴趣的:(Springboot,shiro,jwt,spring,boot)