Spring Boot Actuator 的使用

  Actuator其实是监控Spring Boot应用的一些信息,如依赖关系,路径处理器关系,线程信息,JVM信息等,但是这些信息都是以json格式返回,没有可视化的页面,需要自己去搜索想要了解的详细信息。

  而这些信息可以通过三种方式获取:

    1.Actuator端点方式,Rest请求获取(使用spring-boot-starter-actuator自启动,如果不是web工程,还需要加spring-webmvc的依赖)

    2.远程shell,使用命令查看(使用spring-boot-starter-remote-shell自启动,监听端口为2000,用户名默认为user,密码会在启动日志可以查看到)

    3.JMX方式

  Actuator端点信息:

  

  在使用Actuator端点查看信息时,有如下敏感信息会被隐藏,查看不了(页面出现错误信息的情况

  

  解决的方法是在application.properties加入如下配置:

    management.security.enabled=false

  

  如果我们需要对Actuator进行个性化设置,可在以下几方面进行定制:

    1.端点重命名(避免请求路径冲突或者避免默认请求暴露,例如在application.properties中加上配置:endpoint.beans.id=beanlist)

    2.端点禁用与启用(例如在application.properties中加上配置:endpoint.beans.enabled=false)

    3.自定义度量信息,针对于metrics端点信息

      3.1如果是统计的话,可使用CounterService接口跟GaugeService接口,无需实现,spring已经帮我们实现了,只要引用就好

@Controller
@RequestMapping("/myworld")
public class MyController {

    @Resource
    private CounterService counterService;

    @Resource
    private GaugeService gaugeService;

    @ResponseBody
    @RequestMapping("hello")
    public String hello(){
        // 在metrics端点增加hello request属性(访问计数)
        counterService.increment("hello.request");
        // 属性时间更新
        gaugeService.submit(
                "hello.request.saved", System.currentTimeMillis());
        return  "hello";
    }
}

      3.2如果需要自定义的一些信息,可实现PublicMetrics接口

@Component
public class ApplicationContextMetrics implements PublicMetrics {

    @Resource
    private ApplicationContext context;

    @Override
    public Collection<Metric<?>> metrics() {
        List<Metric<?>> metrics = new ArrayList<Metric<?>>();
        // 启动时间
        metrics.add(new Metric<Long>("spring.context.startup-date",
                context.getStartupDate()));
        // bean的总数量
        metrics.add(new Metric<Integer>("spring.beans",
                context.getBeanNamesForType(Object.class).length));
        // Controller的数量
        metrics.add(new Metric<Integer>("spring.controllers",
                context.getBeanNamesForAnnotation(Controller.class).length));
        return metrics;
    }
}

    4.自定义跟踪数据的仓库(主要针对trace端点,默认保留100条跟踪信息,新来的会覆盖旧有的,我们可以加大容量设为1000或者10000,但还有一种方法就是自定义仓库,实现TraceRepository接口)

     我使用的是redis作为跟踪数据的仓库,数据结构采用的set集合,也可以使用list列表,java实体转换json使用的是fastjson

     引入redis自启动(如果引入不进来,可指定版本号,有可能spring-boot-starter-redis的版本号不及spring-boot版本的新,如redis自启动版本是1,

             而spring-boot的版本是2,默认去找版本号为2的redis自启动是找不到的

     <dependency>  

      <groupId>org.springframework.boot</groupId>  

      <artifactId>spring-boot-starter-redis</artifactId>  

     </dependency

    具体spring-boot的redis使用可参考网上例子,这里不做详细描述,以后可写一篇关于spring redis使用的博文。

    在application.properties加入redis的配置:

# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=200
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=100
spring.redis.pool.max-wait=-1
# 连接超时时间(毫秒)
spring.redis.timeout=3000

    代码如下:

@Component
public class RedisTraceRepository implements TraceRepository {

    @Autowired
    private JedisService jedisService;

    @Override
    public List<Trace> findAll() {
        return jedisService.findObjectSet(Trace.class);
    }

    @Override
    public void add(Map<String, Object> map) {
        jedisService.objectSet((new Trace(new Date(), map)));
    }
}
@Service
public class JedisService {

    private static final Map<String, String> mapNames = new ConcurrentHashMap();

    @Autowired
    private RedisTemplate redisTemplate;

       /**
     * 集合添加
     * @param key
     * @param value
     */
    public void add(String key,Object value){
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        set.add(key,value);
    }

    /**
     * 集合获取
     * @param key
     * @return
     */
    public Set<Object> setMembers(String key){
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        return set.members(key);
    }

    private static final boolean isUpperEnglishChar(char value) {
        return value >= 65 && value <= 90;
    }

    private static final <T> String getSimpleName(String simpleName) {
        char[] chars = simpleName.toCharArray();
        StringBuffer sb = new StringBuffer();

        for(int i = 0; i < chars.length; ++i) {
            char c = chars[i];
            if(i > 0 && isUpperEnglishChar(c)) {
                sb.append("_").append(String.valueOf(c).toLowerCase());
            } else {
                sb.append(String.valueOf(c).toLowerCase());
            }
        }

        return new String(sb);
    }

    public static <T> String getMapName(Class<T> clazz) {
        String name = clazz.getName();
        String mapName = (String)mapNames.get(name);
        if(StringUtils.isEmpty(mapName)) {
            mapName = getSimpleName(clazz.getSimpleName());
            mapNames.put(name, mapName);
        }

        return mapName;
    }


    public <T> void objectSet(final T obj) {
        add(getMapName(obj.getClass()), JSON.toJSONString(obj));
    }

    public <T> List<T> findObjectSet(Class<T> clazz) {
        String key =getMapName(clazz);
        Set<Object> set = setMembers(key);
        List<T> list = new ArrayList<T>();
        String value;
        for(Object t :set){
            value =t.toString();
            if(value != null) {
                list.add(JSON.parseObject(value,clazz));
            }
        }
        return  list;
    }
}

     5.自定义健康指示器信息(主要针对于health端点,可实现HealthIndicator接口)

@Component
public class BodyHealth implements HealthIndicator {

    @Override
    public Health health() {
        try {
            return Health.up().withDetail("reason", "吃饱了很健康").build();
        } catch (Exception e) {
            return Health.down().withDetail("reason", "吃到一半拉肚子了").build();
        }
    }
}

   actuator访问控制:

  像rest直接请求端点信息,未免太不安全,所有人都可以看你工程的信息了,特别是/shutdown这请求,直接关了你的工程,那很嗨了。

   所以接下来,这里结合spring security做端点访问的控制。

     首先引入依赖:

  <dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-security</artifactId>

  </dependency>

  在application.properties加入配置:

management.context-path=/admin
management.security.enabled=true

# 测试用户
security.user.name=admin
security.user.password=admin
security.basic.enabled=true

# 测试用户角色
management.security.roles=ADMIN

  在写一个自定义的WebSecurityConfigurerAdapter,用于实现自己的定制实现:

package com.zgz.security;

import com.alibaba.druid.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Collection;

/**
 * Created by zgz on 2017/12/16.
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    Environment env;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").access("hasRole('READER')")
                    // 这里对/admin/**的请求都判断有没有ADMIN角色,还记得配置文件配了对actuator端点的访问基路径吗(/admin),
                    // 没错,为了不影响其他请求,这里只对actuator端点进行鉴权
                .antMatchers("/admin/**").access("hasRole('ADMIN')")
                .antMatchers("/**").permitAll().and().formLogin().and().httpBasic();;
    }

    /** 如果不想在配置文件添加测试用户,也可在这里添加用户
    @Override
    protected void configure(
            AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin").password("admin")
                .roles("ADMIN");
    }
    **/
}

  以上这里只是为了举例实现对actuator的访问控制,其实spring security还有很多可说,这里就不做详述,以后可再写一篇关于spring security的博文。

  以上纯属个人整理跟理解,如有错误,请帮忙纠正我,在此感谢!

原文地址:https://www.cnblogs.com/zgz21/p/8038107.html