spring security小ネタ

猫好きリーマン

こんにちわ、猫好きリーマンのほげPGです。

3つほど小ネタ紹介です。

1,  ログインユーザをログ出力する

仕組み:セッションに認証情報が保存されているので、リクエストの入り口で認証情報を取得しMDC.put()するだけ。

フィルター

@Component
@Slf4j
public class HogeLoggingFilter extends CommonsRequestLoggingFilter {

    @Override
    protected boolean shouldLog(HttpServletRequest request) {
        String uri = request.getServletPath();
        return !checkStaticPath(uri);
    }

    private boolean checkStaticPath(String path) {
        return path.startsWith("/static/");
    }

    @Override
    protected void beforeRequest(HttpServletRequest request, String message) {
        LoginUser user = getLoginUser(request.getSession(false));
        if (user != null) {
            MDC.put("USER-ID", user.getId());
        }
    }

    public LoginUser getLoginUser(HttpSession session) {
        if (session != null) {
            SecurityContext securityContext = (SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT");
            if (securityContext != null) {
                Authentication authentication = securityContext.getAuthentication();
                return (LoginUser) authentication.getPrincipal();
            }
        }
        return null;
    }

    @Override
    protected void afterRequest(HttpServletRequest request, String message) {
        MDC.remove("USER-ID");
    }
}

※継承元が用途に合っていないのでjavax.servlet.Filter を使った方が良いかも

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.out</target>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %5.5thread %X{USER-ID} \(%file:%line\) %M - %msg%n</pattern>
        </encoder>
    </appender>

ログ定義

2,  更新系SQLだけログ出力する

仕組み:mybatis限定になるが、Mapperのメソッド名を更新系はinsert/update/deleteで始まるメソッド名にし、ログフィルターでロガー名から更新系か判断する。

ログフィルター

public class SqlLogFilter extends Filter<ILoggingEvent> {
    private static final List<String> DEFAULT_ACCEPT = List.of("insert", "update", "delete");

    private static boolean checkName(String name) {
        for (String s : DEFAULT_ACCEPT) {
            if (name.startsWith(s)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public FilterReply decide(ILoggingEvent event) {
        String loggerName = event.getLoggerName();
        if (loggerName.startsWith("jp.co.ois.hoge.springsecurity.mapper")) {
            int ix = loggerName.lastIndexOf(".");
            String name = loggerName.substring(ix + 1);
            if (!checkName(name)) {
                return FilterReply.DENY;
            }
        }
        return FilterReply.ACCEPT;
    }
}

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.out</target>
        …
        <filter class="jp.co.ois.hoge.springsecurity.util.SqlLogFilter" />
    </appender>

ログ定義

3,  起動時にwarのバージョンをログ出力する

仕組み:起動終了リスナーでServletContextEventからMANIFEST.MFを参照し、ログ出力する。

@WebListener
@Slf4j
public class HogeContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("Starting.");
        ServletContext sc = sce.getServletContext();
        String metafile = "/META-INF/MANIFEST.MF";
        try (InputStream is = sc.getResourceAsStream(metafile)) {
            byte[] bytes = is.readAllBytes();
            String meta = new String(bytes, StandardCharsets.UTF_8);
            meta.lines()
                .filter(s -> s.startsWith("Implementation-Version"))
                .forEach(this::info);
        } catch (IOException e) {
            log.error(e.toString(), e);
        }
    }
    
    private void info(String s) {
        log.info(s);
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("Shutdown.");
    }
}

起動終了リスナー

プロジェクト一式を添付しておきます。