有手就行的 Spring Boot 集成 Shiro

17 篇文章 0 订阅
3 篇文章 0 订阅

前言

  Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。

  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。这不就是我们想要的嘛,而且 Shiro 的 API 也是非常简单;其基本功能点如下图所示:
    clipboard.png

  • Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;

  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

  • Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;

  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

  • Web Support:Web 支持,可以非常容易的集成到 Web 环境;

  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;

  • Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
    在这里插入图片描述

Spring Boot 集成 Shiro

pom.xml

        <!--Shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>${shiro.version}</version>
        </dependency>

ShiroConfig

/**
 * @ClassName ShiroConfig
 * @Author zwy
 * @Data 1/4/2021 下午4:31
 */
@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
       ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

       shiroFilterFactoryBean.setFilterChainDefinitionMap((Map<String, String>) new HashMap<>().put("jwtFilter",new JwtFilter()));

        // 添加 jwt 专用过滤器,拦截除 /login 和 /logout 外的请求
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("jwtFilter", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        Map<String, String> linkedHashMap = new LinkedHashMap<>();

        linkedHashMap.put("/doc.html","anon");
        //设置不拦截druid
        linkedHashMap.put("/druid/**","anon");

        //Swagger的所有请求的资源和请求的地址都不需要拦截
        linkedHashMap.put("/swagger/**","anon");
        linkedHashMap.put("/v2/api-docs","anon");
        linkedHashMap.put("/swagger-ui.html","anon");
        linkedHashMap.put("/swagger-resources/**","anon");
        linkedHashMap.put("/webjars/**","anon");
        linkedHashMap.put("/csrf","anon");
        linkedHashMap.put("/login","anon");
        linkedHashMap.put("/captcha.jpg","anon");
        linkedHashMap.put("/email","anon");
        linkedHashMap.put("/register","anon");
        linkedHashMap.put("/**","jwtFilter,authc");
       shiroFilterFactoryBean.setFilterChainDefinitionMap(linkedHashMap);

       shiroFilterFactoryBean.setSecurityManager(securityManager());
       return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        Set<Realm> set = new HashSet<>();
        set.add(MyRealm());
        set.add(TokenRealm());
        defaultWebSecurityManager.setRealms(set);
        return defaultWebSecurityManager;
    }

    /**
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }


    /**
     * Token 认证
     * @return
     */
    @Bean
    public Realm TokenRealm(){
        TokenRealm shiroRealm = new TokenRealm();
        return  shiroRealm;
    }

    /**
     * 账号认证
     * @return
     */
    @Bean
    public Realm MyRealm(){
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(credentialsMatcher());
        return  shiroRealm;
    }

    @Bean
    public CredentialsMatcher credentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName(LogicUtil.hashName);
        hashedCredentialsMatcher.setHashIterations(LogicUtil.hashIterations);
        return hashedCredentialsMatcher;
    }

}

TokenRealm(token认证)

/**
 * @author lanys
 * @Description:
 * @date 22/6/2021 下午3:00
 */
public class TokenRealm extends AuthorizingRealm {

    @Autowired
    private SysMenuServer menuServer;

    @Autowired
    private SysUserService userService;

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


    /**
     * 管理权限
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //得登录的用户名
        SysUser username = (SysUser) principals.getPrimaryPrincipal();
        System.out.println(("username:"+username.getUsername()));
        Set<String> permissionSet = new HashSet<>();
        List<String> permissions;
        if (Constants.SUPER_ADMIN.equals(username)){
            permissions = menuServer.findPermissionsByRole();
            if (permissions != null) {
                for (String permission : permissions) {
                    if (StrUtil.isNotEmpty(permission)) {
                        permissionSet.add(permission);
                    }
                }
            }
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissionSet);
        return info;
    }

    /**
     * 管理登录  如果有异常,则令牌无效
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //得到客户端传过来的令牌
        String tokenString =(String) token.getCredentials();
        //得到
        String principal = (String) token.getPrincipal();
        SysUser sysUser = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, principal));
        //根据令牌得用户名,可以查用户库
        if (tokenString==null&&sysUser==null)
        {
            throw new RuntimeException("登录失败");
        }
        return new SimpleAuthenticationInfo(sysUser, tokenString, getName());
    }
}

ShiroRealm(账号密码认证)

/**
 * @ClassName ShiroRealm
 * @Author zwy
 * @Data 1/4/2021 下午4:37
 */
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private SysUserService userService;
    
    @Autowired
    private SysMenuServer menuServer;

    /**
     * 授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限授权");
        Set<String> permissionSet = new HashSet<>();

        List<String> permissions;
        SysUser userName = (SysUser) principals.getPrimaryPrincipal();
        if (Constants.SUPER_ADMIN.equals(userName.getUsername())){
            permissions = menuServer.findPermissionsByRole();
            if (permissions != null) {
                for (String permission : permissions) {
                    if (StrUtil.isNotEmpty(permission)) {
                        permissionSet.add(permission);
                    }
                }
            }
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissionSet);
        return info;
    }

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String) token.getPrincipal();
        LambdaQueryWrapper<SysUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(SysUser::getUsername,principal);
        SysUser user = userService.getOne(lambdaQueryWrapper);
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(),new Md5Hash(user.getSalt()),getName());
        return simpleAuthenticationInfo;
    }

    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        super.setCredentialsMatcher(credentialsMatcher);
    }

}

JwtToken(token认证实体)

/**
 * @author lanys
 * @Description:
 * @date 21/6/2021 下午12:52
 */

public class JwtToken implements AuthenticationToken {

    private static final long serialVersionUID = 1L;

    // 加密后的 JWT token串
    private String token;

    private String userName;

    public JwtToken(String token) {
        this.token = token;
        this.userName = TokenUtil.getClaimFiled(token, Constants.jwtTokenSecret);
    }

    @Override
    public Object getPrincipal() {
        return this.userName;
    }

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

JwtFilter(token过滤器)

/**
 * @author lanys
 * @Description:
 * @date 21/6/2021 上午11:42
 */
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {


    private String getToken(ServletRequest request, ServletResponse response) {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        String authorization = servletRequest.getHeader("Authorization");
        if (authorization==null){
            return null;
        }
        String token = authorization.replace("Bearer ", "");
        return token;
    }

    /**
     * 前置处理
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
        String token = getToken(request, response);
        System.out.println(((HttpServletRequest) request).getHeader("Authorization"));
        //判断请求的请求头是否带上 "Token"
        if (token != null) {
            //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
            try {
                executeLogin(request, response);
                return true;
            } catch (AuthenticationException e) {
                //token 错误
                return false;
            } catch (Exception e) {
                return false;
            }
        }
        //如果请求头不存在 Token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
        return true;
    }


    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        String token = getToken(request, response);
        if (token != null && TokenUtil.isTokenExpired(token) == true) {
            try {
                log.info("提交UserModularRealmAuthenticator决定由哪个realm执行操作...");
                JwtToken jwtToken = new JwtToken(token);
                // 提交给realm进行登入,如果错误他会抛出异常并被捕获
                getSubject(request, response).login(jwtToken);
            } catch (Exception e) {
                log.info("捕获到身份认证异常");
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().write(Result.fail("Token失效,请重新登录...").toString());
                return false;
            }
        } else {
            log.info("Token失效...");
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(Result.fail("Token失效,请重新登录...").toString());
            return false;
        }
        // 如果没有抛出异常则代表登入成功,返回true
        return true;

    }

    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        String token = servletRequest.getHeader("Authorization");
        return new JwtToken(token);
    }
}

LoginController(登录)

 @ApiOperation(value = "登录")
    @ResponseBody
    @GetMapping("/login")
    public Result login(String username, String password,String captcha) {
//        ShiroUtil.getKaptcha(Constants.KAPTCHA_SESSION_KEY,captcha);
        if (StringUtils.isAllBlank(username, password,captcha)) {
            return Result.fail("数据不能为空");
        }
        SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password));
        String token = TokenUtil.doGenerateToken(SecurityLogic.getName(),SecurityLogic.getPassword());
        EhcacheMap.getInstance().put(token,token);
        return Result.success().put(token);
    }

TokenUtil(生成token&检查是否过期)

/**
 * @ClassName TokenUtil
 * @Author zwy
 * @Data 1/4/2021 下午7:05
 */
@Slf4j
public class TokenUtil {
    
    /**
     * 解析
     * @param token
     * @return
     */
    public static Claims getClaimFromToken(String token) {
        return Jwts.parser().setSigningKey(Constants.jwtTokenSecret).parseClaimsJws(token).getBody();
    }

    /**
     * 获得token中的自定义信息,无需secret解密也能获得
     */
    public static String getClaimFiled(String token, String filed) {
        try {
            return Jwts.parser().setSigningKey(filed).parseClaimsJws(token).getBody().getSubject();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 获取用户
     * @param token
     * @return
     */
    public static String getName(String token){
        return Jwts.parser().setSigningKey(Constants.jwtTokenSecret).parseClaimsJws(token).getBody().getSubject();
    }


    /**
     * 判断过期
     *
     * @param token
     * @return
     */
    public static Boolean isTokenExpired(String token) {
        try {
            final Date expiration = getClaimFromToken(token).getExpiration();
            log.info("token到期时间:"+expiration);
            return true;
        } catch (Exception e) {
            log.info("token过期");
            return false;
        }
    }


    /**
     * 生成
     *
     * @param username
     * @return
     */
    public static String doGenerateToken(String username,String password) {
        final Date createdDate = new Date();
        //Constants.time * 1000
        final Date finishDate = new Date(createdDate.getTime() + 86400);

        return Jwts.builder()
                .setSubject(username)
                .setAudience(password)
                .setIssuedAt(createdDate)
                .setExpiration(finishDate)
                .signWith(SignatureAlgorithm.HS256, Constants.jwtTokenSecret).compact();
    }

}

测试

在这里插入图片描述

Token验证

在这里插入图片描述

总结

Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。
Shiro特点:

  • 易于理解的 Java Security API;
  • 简单的身份认证(登录),支持多种数据源(LDAP,JDBC 等);
  • 对角色的简单的签权(访问控制),也支持细粒度的鉴权;
  • 支持一级缓存,以提升应用程序的性能;
  • 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
  • 异构客户端会话访问;
  • 非常简单的加密 API(Shiro加密后的密码是解析不出来的);
  • 不跟任何的框架或者容器捆绑,可以独立运行。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值