SpringBoot如何集成Shiro呢?

戚薇 SpringBoot 发布时间:2023-05-13 09:28:16 阅读数:14098 1
下文笔者讲述SpringBoot中集成Shiro的方法及示例分享,如下所示

Shiro简介

Shiro是Apache下的一个开源项目
Shiro是一个功能强大的安全框架,对外提供认证、授权、加密和会话管理等功能
    对于任何一个应用程序
    Shiro都可以提供全面的安全管理服务
而且Shiro这个框架使用非常简单 
=====================================================
那么如何让这个优秀的框架集成到Shiro中呢?
下文笔者将一一道来具体的集成方法,如下所示
集成步骤:
  1.导入Shiro相关依赖
  2.ShiroConfig
  3.新建一个Realm类
  4.新建一个JWTFilter
  5.JwtToken
  6.JwtUtils
  7.全局异常处理
  8.测试Controller

shiro所需依赖

导入
shiro-redis的starter包
jwt工具包
hutool工具包 
<!--  shiro      -->
<dependency>
	<groupId>org.crazycake</groupId>
	<artifactId>shiro-redis-spring-boot-starter</artifactId>
	<version>3.3.1</version>
</dependency>
<!--   huitool工具类     -->
<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>5.8.3</version>
</dependency>
<!--   jwt     -->
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt</artifactId>
	<version>0.9.1</version>
</dependency>

ShiroConfig代码的编写

@Configuration
public class ShiroConfig {

    @Autowired
    JwtFilter jwtFilter;

    @Bean
    public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();

        // inject redisSessionDAO
        sessionManager.setSessionDAO(redisSessionDAO);

        // other stuff...

        return sessionManager;
    }


    @Bean
    public DefaultWebSecurityManager  securityManager(AccountRealm accountRealm, SessionManager sessionManager, RedisCacheManager redisCacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);
        securityManager.setSessionManager(sessionManager);
        securityManager.setCacheManager(redisCacheManager);
        /*
         * 关闭shiro自带的session
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }


    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        Map<String,String> filterMap = new LinkedHashMap<>();
        //主要通过注解方式校验权限
        filterMap.put("/**","jwt");
        chainDefinition.addPathDefinitions(filterMap);
        return chainDefinition;
    }

    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        Map<String, Filter> filters = new HashMap<>();
        filters.put("jwt", jwtFilter);
        shiroFilter.setFilters(filters);
        Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }

}
RedisSessionDAO和RedisCacheManager:
  其功能将shiro中权限数据和会话信息能保存到redis中,实现会话共享

重写SessionManager和DefaultWebSecurityManager
   同时DefaultWebSecurityManager中关闭shiro自带的session方式
   需设置为false
   此时用户就不再能通过session方式登录shiro
   将采用jwt凭证登录。
在ShiroFilterChainDefinition中
     我们不再通过编码形式拦截Controller访问路径
     而是所有的路由都需要经过JwtFilter这个过滤器
     然后判断请求头中是否含有jwt的信息
    有就登录,没有就跳过。跳过之后
    有Controller中的shiro注解进行再次拦截
     如@RequiresAuthentication,这样控制权限访问。

新建一个Realm类

新建realm类:
  需继承Shiro包下AuthorizingRealm类
   并重写doGetAuthorizationInfo和doGetAuthenticationInfo方法

doGetAuthorizationInfo获取身份信息:
    在这个方法中
     可从数据库获取该用户的权限和角色信息
     当调用权限验证时,就会调用此方法

doGetAuthenticationInfo:
    在这个方法中
     进行身份验证
     login时调用
例:Realm的源码
@Component
public class AccountRealm extends AuthorizingRealm {

    @Autowired
    JwtUtils jwtUtils;
    @Autowired
    CardService cardService;

    @Override
    public boolean supports(AuthenticationToken token) {
        //realm支持jwt的凭证校验
        return token instanceof JwtToken;
    }

    /**
     *获取身份信息,我们可以在这个方法中,从数据库获取该用户的权限和角色信息 当调用权限验证时,就会调用此方法
     * */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     * 在这个方法中,进行身份验证 login时调用
     * */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        JwtToken jwtToken = (JwtToken) authenticationToken;

        //这里演示就随便写的接口 到时候这里写你们登录验证时的业务操作即可
        String userId = jwtUtils.getClaimByToken((String) jwtToken.getPrincipal()).getSubject();
        Card card = cardService.getById(Long.valueOf(userId));
        if(card == null){
            throw new UnknownAccountException("账户不存在");
        }

        AccountProfile accountProfile = new AccountProfile();
        BeanUtil.copyProperties(card,accountProfile);

        return new SimpleAuthenticationInfo(accountProfile,jwtToken.getCredentials(),getName());
    }
}

doGetAuthenticationInfo登录认证方法简介:
  通过jwt获取到用户信息
   判断用户的状态
    最后异常就抛出对应的异常信息
     否者封装成SimpleAuthenticationInfo返回给shiro

AccountProfile定义

AccountProfile 
   根据自身业务填写参数
     登录成功之后返回的一个用户信息的载体
   说白了就是一个用户的实体类

@Data
public class AccountProfile implements Serializable {
    private Long id;
    private Date addTime;
    private String phone;
    private String name;
}
 

创建JWTFilter实体类

@Component
public class JwtFilter extends AuthenticatingFilter {

    @Autowired
    JwtUtils jwtUtils;

    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

        //将servletRequest强转为HttpServletRequest
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String jwt = httpServletRequest.getHeader("Authorization");
        if (jwt.isEmpty()){
            return null;
        }
        return new JwtToken(jwt);
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {


        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String jwt = httpServletRequest.getHeader("Authorization");
        //这里为空将不再进行shiro登录处理 直接交给注解拦截就行了
        if (StringUtils.isEmpty(jwt)){
            return true;
        }else {

            //校验jwt
            Claims claim = jwtUtils.getClaimByToken(jwt);
            if(claim == null || jwtUtils.isTokenExpired(claim.getExpiration())){
                throw new ExpiredCredentialsException("token已经失效,请重新登录");
            }
            //执行登录处理
            return executeLogin(servletRequest,servletResponse);
        }
    }


    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {

        //将异常返回给前端

        HttpServletResponse servletResponse = (HttpServletResponse) response;

        Throwable throwable = e.getCause() == null ? e : e.getCause();
        Result result = new Result(400, throwable.getMessage(), null);
        String jsonStr = JSONUtil.toJsonStr(result);

        try {

            servletResponse.getWriter().print(jsonStr);
        } catch (IOException ex) {

        }
        return false;
    }
}

JwtToken

创建一个jwtToken实现AuthenticationToken
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;
    }
}

JwtUtils

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * jwt工具类
 */
@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "jwtutils.jwt")
public class JwtUtils {

    private String secret;
    private long expire;
    private String header;

    /**
     * 生成jwt token
     */
    public String generateToken(long userId) {
        Date nowDate = new Date();
        //过期时间
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);

        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(userId+"")
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    public Claims getClaimByToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        }catch (Exception e){
            log.debug("validate is token error ", e);
            return null;
        }
    }

    /**
     * token是否过期
     * @return  true:过期
     */
    public boolean isTokenExpired(Date expiration) {
        return expiration.before(new Date());
    }
}

全局异常类编写

@RestControllerAdvice
public class GlobalExceptionHeadler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = RuntimeException.class)
    public Result headler(RuntimeException e){
        return new Result(400,e.getMessage(),null);
    }

    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(value = ShiroException.class)
    public Result headler(ShiroException e){
        return new Result(401,e.getMessage(),null);
    }
}

全局返回对象定义

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {

    private Integer code;

    private String message;

    private T data;

    public Result(Integer code,String message){
        this(code,message,null);
    }

}

配置文件

shiro-redis:
  enabled: true
  redis-manager:
    host: 127.0.0.1:6379

jwtutils:
  jwt:
    #加密密钥
    secret: f1231x545as21xc5a4sz2c1sa
    #token有效时间 单位秒
    expire: 604800
    header: Authorization

编写Controller测试

@RequiresAuthentication
@GetMapping("/test")
@ResponseBody
public Result<list<UserList>> getUserList(){

}
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

本文链接: https://www.Java265.com/JavaFramework/SpringBoot/202305/6428.html

最近发表

热门文章

好文推荐

Java265.com

https://www.java265.com

站长统计|粤ICP备14097017号-3

Powered By Java265.com信息维护小组

使用手机扫描二维码

关注我们看更多资讯

java爱好者