Springboot Actuator의 10: actuator의 audit 패키지
2. 원본 코드 해석
2.1 AuditEvent 이벤트 클래스
Audit Event – > 1개의 값 대상 -> 1개의audit 이벤트를 대표합니다: 특정한 시간, 1개의 특정한 사용자 또는 에이전트에서 1개의 특정한 유형의 동작을 실시합니다.Audit Event는 Audit Event에 관한 세부 사항을 기록했다.
클래스에는 다음과 같은 메모가 있습니다.
@JsonInclude(Include.NON_EMPTY)
이 클래스가 비어 있거나 null인 속성은 서열화되지 않습니다.
클래스 필드는 다음과 같습니다.
private final Date timestamp;
//
private final String principal;
private final String type;
private final Map data;
2.2 AuditApplication Event 이벤트 클래스
AuditApplication Event – > 패키지 AuditEvent.코드는 다음과 같습니다.
public class AuditApplicationEvent extends ApplicationEvent {
private final AuditEvent auditEvent;
public AuditApplicationEvent(String principal, String type,
Map data) {
this(new AuditEvent(principal, type, data));
}
AuditApplicationEvent(String principal, String type, String... data) {
this(new AuditEvent(principal, type, data));
}
public AuditApplicationEvent(Date timestamp, String principal, String type,
Map data) {
this(new AuditEvent(timestamp, principal, type, data));
}
public AuditApplicationEvent(AuditEvent auditEvent) {
super(auditEvent);
Assert.notNull(auditEvent, "AuditEvent must not be null");
this.auditEvent = auditEvent;
}
public AuditEvent getAuditEvent() {
return this.auditEvent;
}
}
2.3、AbstractAuditListener
AbstractAuditListener – > AuditApplication Event 이벤트의 추상적인 클래스를 처리합니다.코드는 다음과 같습니다.
public abstract class AbstractAuditListener
implements ApplicationListener {
@Override
public void onApplicationEvent(AuditApplicationEvent event) {
onAuditEvent(event.getAuditEvent());
}
protected abstract void onAuditEvent(AuditEvent event);
}
2.4、AuditEventRepository
Audit Event Repository –>Audit Event에 대한dao 실현.다음과 같은 네 가지 방법이 설명되어 있습니다.
//
void add(AuditEvent event);
// AuditEvent
List find(Date after);
// Date principal( ) AuditEvent
List find(String principal, Date after);
// date,principal,type AuditEvent
List find(String principal, Date after, String type);
2.5、InMemoryAuditEventRepository
Memory Audit Event Repository –> Audit Event Repository 인터페이스의 유일한 실현.
클래스 필드는 다음과 같습니다.
// AuditEvent
private static final int DEFAULT_CAPACITY = 4000;
// events
private final Object monitor = new Object();
/**
* Circular buffer of the event with tail pointing to the last element.
*
*/
private AuditEvent[] events;
// 1
private volatile int tail = -1;
작성자는 다음과 같습니다.
public InMemoryAuditEventRepository() {
this(DEFAULT_CAPACITY);
}
public InMemoryAuditEventRepository(int capacity) {
this.events = new AuditEvent[capacity];
}
AuditEventRepository 의 방법은 다음과 같습니다.
@Override
public void add(AuditEvent event) {
Assert.notNull(event, "AuditEvent must not be null");
synchronized (this.monitor) {
this.tail = (this.tail + 1) % this.events.length;
this.events[this.tail] = event;
}
}
@Override
public List find(Date after) {
return find(null, after, null);
}
@Override
public List find(String principal, Date after) {
return find(principal, after, null);
}
//
@Override
public List find(String principal, Date after, String type) {
LinkedList events = new LinkedList();
synchronized (this.monitor) {
// 1. events
for (int i = 0; i < this.events.length; i++) {
// 1.1 AuditEvent
AuditEvent event = resolveTailEvent(i);
// 1.2 AuditEvent null , events
if (event != null && isMatch(principal, after, type, event)) {
events.addFirst(event);
}
}
}
// 2.
return events;
}
//
private boolean isMatch(String principal, Date after, String type, AuditEvent event) {
boolean match = true;
match = match && (principal == null || event.getPrincipal().equals(principal));
match = match && (after == null || event.getTimestamp().compareTo(after) >= 0);
match = match && (type == null || event.getType().equals(type));
return match;
}
// AuditEvent
private AuditEvent resolveTailEvent(int offset) {
int index = ((this.tail + this.events.length - offset) % this.events.length);
return this.events[index];
}
결과 세트로 돌아가기
여기에는 두 가지 질문이 있습니다.
1. 앞에서 말했듯이 이벤트에 접근할 때 잠금을 추가해야 하는데 왜 ResolveTail Event 방법은 잠금을 추가하지 않았습니까?
이유는 다음과 같다.resolveTail Event의 호출점은 하나뿐이다.find(String Date,String)에 있고 이 방법에는 이미 자물쇠가 설치되어 있기 때문에 이 방법은 자물쇠를 추가할 필요가 없다.
2. resolveTail Event 방법에 자물쇠를 채워도 됩니까
답:네,synchronized는 다시 들어갈 수 있기 때문입니다.하지만 추천하지 않습니다. 더하면 성능 손실이 발생합니다.
이 방법의 실현 원리에 관해서 우리는 그래도 예를 드는 것이 비교적 좋다.배열 길이가 3개라고 가정하면 다음과 같이 배열이 가득 채워집니다.
[0,1,2]
이제 tail = 2, 그리고 3을 계속 넣으면 배열은 다음과 같습니다.
[3,1,2], 이때tail=0.그리고 우리는find를 호출합니다.이 메서드에서 resolveTailEvent가 호출됩니다.
첫 번째 전송이 0이면 index=(0+3-0)%3=0, 획득한 것이 바로 3.두 번째로 들어온 것이 1이면 index=(0+3-1)%3=2로 얻은 것이 바로 2.3번째 들어온 것이 2이면 index=(0+3-2)%3=1이고 획득한 것이 바로 1.따라서find(String, Date, String)가 얻은 결과는 추가된 순서에 따라 역순으로 되돌아온다.
자동 어셈블:
AuditAutoConfiguration 클래스 내의 static AuditEvent Repository Configuration 클래스에 설명된 코드는 다음과 같습니다.
@ConditionalOnMissingBean(AuditEventRepository.class)
protected static class AuditEventRepositoryConfiguration {
@Bean
public InMemoryAuditEventRepository auditEventRepository() throws Exception {
return new InMemoryAuditEventRepository();
}
}
beanFactory에Audit Event Repository 유형의 bean이 존재하지 않을 때 적용됩니다.ID 1개는 audit Event Repository, InMemory Audit Event Repository 유형은 bean.
2.6、AuditListener
AuditListener –> AbstractAuditListener의 기본 구현AuditApplication Event 이벤트를 감청하여AuditEvent Repository에 저장합니다.코드는 다음과 같습니다.
public class AuditListener extends AbstractAuditListener {
private static final Log logger = LogFactory.getLog(AuditListener.class);
private final AuditEventRepository auditEventRepository;
public AuditListener(AuditEventRepository auditEventRepository) {
this.auditEventRepository = auditEventRepository;
}
@Override
protected void onAuditEvent(AuditEvent event) {
if (logger.isDebugEnabled()) {
logger.debug(event);
}
this.auditEventRepository.add(event);
}
}
AuditApplication Event를 감청할 때, 봉인된 Audit Event를 Audit Event Repository에 직접 추가합니다.그래도 쉬워요.
자동 어셈블은 다음과 같습니다.
AuditAutoConfiguration에서 다음과 같은 코드로 선언되었습니다.
@Bean
@ConditionalOnMissingBean(AbstractAuditListener.class)
public AuditListener auditListener() throws Exception {
return new AuditListener(this.auditEventRepository);
}
@Bean –> 등록 ID 1개는auditListener, 유형은AuditListener의bean@ConditionalOnMissingBean(AbstractAuditListener.class) -> BeanFactory에 AbstractAuditListener 유형의 bean이 존재하지 않을 때 적용됩니다.
AuditListener에 InMemory Audit Event Repository가 주입되어 있음을 주의하십시오
2.7AbstractAuthenticationAuditListener
Abstract Authentication Audit Listener – > Spring Security Abstract Authentication Event(인증 이벤트)를 Audit Event의 추상적인 Application Listener 기류로 변환하는 것을 폭로합니다.
코드는 다음과 같습니다.
public abstract class AbstractAuthenticationAuditListener implements
ApplicationListener, ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
protected ApplicationEventPublisher getPublisher() {
return this.publisher;
}
protected void publish(AuditEvent event) {
if (getPublisher() != null) {
getPublisher().publishEvent(new AuditApplicationEvent(event));
}
}
}
2.8、AuthenticationAuditListener
AuthenticationAuditListener의 기본 구현 필드는 다음과 같습니다.
// AuthenticationSuccessEvent AuditEvent type
public static final String AUTHENTICATION_SUCCESS = "AUTHENTICATION_SUCCESS";
// AbstractAuthenticationFailureEvent AuditEvent type
public static final String AUTHENTICATION_FAILURE = "AUTHENTICATION_FAILURE";
// AuthenticationSwitchUserEvent AuditEvent type
public static final String AUTHENTICATION_SWITCH = "AUTHENTICATION_SWITCH";
private static final String WEB_LISTENER_CHECK_CLASS = "org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent";
private WebAuditListener webListener = maybeCreateWebListener();
// spring-boot-starter-security , org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent
// WebAuditListener
private static WebAuditListener maybeCreateWebListener() {
if (ClassUtils.isPresent(WEB_LISTENER_CHECK_CLASS, null)) {
return new WebAuditListener();
}
return null;
}
onApplicationEvent 방법은 다음과 같습니다.
public void onApplicationEvent(AbstractAuthenticationEvent event) {
// 1. ,
if (event instanceof AbstractAuthenticationFailureEvent) {
onAuthenticationFailureEvent((AbstractAuthenticationFailureEvent) event);
}
// 2. webListener null. AuthenticationSwitchUserEvent
else if (this.webListener != null && this.webListener.accepts(event)) {
this.webListener.process(this, event);
}
// 3. AuthenticationSuccessEvent
else if (event instanceof AuthenticationSuccessEvent) {
onAuthenticationSuccessEvent((AuthenticationSuccessEvent) event);
}
}
1. 검증이 실패하면(Abstract Authentication Failure Event) Audit Event 이벤트를 보내고 type은 AUTHENTICATIONFAILURE.코드는 다음과 같습니다.
private void onAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) {
Map data = new HashMap();
data.put("type", event.getException().getClass().getName());
data.put("message", event.getException().getMessage());
if (event.getAuthentication().getDetails() != null) {
data.put("details", event.getAuthentication().getDetails());
}
publish(new AuditEvent(event.getAuthentication().getName(),
AUTHENTICATION_FAILURE, data));
}
만약 웹리스트가 null과 같지 않다면.또한 이 이벤트가 AuthenticationSwitchUser Event이면 AuditEvent 이벤트를 보내고 type은 AUTHENTICATIONSWITCH.코드는 다음과 같습니다.
public void process(AuthenticationAuditListener listener,
AbstractAuthenticationEvent input) {
if (listener != null) {
AuthenticationSwitchUserEvent event = (AuthenticationSwitchUserEvent) input;
Map data = new HashMap();
if (event.getAuthentication().getDetails() != null) {
data.put("details", event.getAuthentication().getDetails());
}
data.put("target", event.getTargetUser().getUsername());
listener.publish(new AuditEvent(event.getAuthentication().getName(),
AUTHENTICATION_SWITCH, data));
}
}
3、AuthenticationSuccess Event이면 Audit Event 이벤트를 보내고 type은 AUTHENTICATIONSUCCESS.코드는 다음과 같습니다.
private void onAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
Map data = new HashMap();
if (event.getAuthentication().getDetails() != null) {
data.put("details", event.getAuthentication().getDetails());
}
publish(new AuditEvent(event.getAuthentication().getName(),
AUTHENTICATION_SUCCESS, data));
}
자동 어셈블:
AuditAutoConfiguration에서 다음과 같은 코드로 선언되었습니다.
@Bean
@ConditionalOnClass(name = "org.springframework.security.authentication.event.AbstractAuthenticationEvent")
@ConditionalOnMissingBean(AbstractAuthenticationAuditListener.class)
public AuthenticationAuditListener authenticationAuditListener() throws Exception {
return new AuthenticationAuditListener();
}
1. @Bean –> 1개의 id를 authenticationAuditListener, AuthenticationAuditListener의 bean으로 등록
2. @Conditional Onclass(name = "org.springframework.security.authentication.event.Abstract Authentication Event") -> 현재 클래스 경로에 org가 존재하는 경우.springframework.security.authentication.event.Abstract Authentication Event 시 효력이 발생합니다 3, @Conditional Onmissing Bean(Abstract Authentication Audit Listener.class) ->bean Factory에 Abstract Authentication Audit Listener 유형의 bean이 존재하지 않을 때 효력이 발생합니다.
2.9、AbstractAuthorizationAuditListener
Abstract Authorization Audit Listener – > Abstract Authorization Event(권한 수여 이벤트)가 Audit Event의 추상적인 Application Listener 기류로 노출된 1개코드는 다음과 같습니다.
public abstract class AbstractAuthorizationAuditListener implements
ApplicationListener, ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
protected ApplicationEventPublisher getPublisher() {
return this.publisher;
}
protected void publish(AuditEvent event) {
if (getPublisher() != null) {
getPublisher().publishEvent(new AuditApplicationEvent(event));
}
}
}
2.10、AuthorizationAuditListener
AuthorizationAuditListener – > AbstractAuthorizationAuditListener의 기본 구현 필드는 다음과 같습니다.
// AuthorizationFailureEvent AuditEvent
public static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";
onApplicationEvent 코드는 다음과 같습니다.
public void onApplicationEvent(AbstractAuthorizationEvent event) {
// 1. AuthenticationCredentialsNotFoundEvent , AuditEvent ,type AUTHENTICATION_FAILURE
if (event instanceof AuthenticationCredentialsNotFoundEvent) {
onAuthenticationCredentialsNotFoundEvent(
(AuthenticationCredentialsNotFoundEvent) event);
}
// 2. AuthorizationFailureEvent , AuditEvent ,type AUTHORIZATION_FAILURE
else if (event instanceof AuthorizationFailureEvent) {
onAuthorizationFailureEvent((AuthorizationFailureEvent) event);
}
}
1、AuthenticationCredentialsNotFoundEvent 이벤트인 경우 AuditEvent 이벤트를 보내고, type은 AUTHENTICATIONFAILURE.코드는 다음과 같습니다.
private void onAuthenticationCredentialsNotFoundEvent(
AuthenticationCredentialsNotFoundEvent event) {
Map data = new HashMap();
data.put("type", event.getCredentialsNotFoundException().getClass().getName());
data.put("message", event.getCredentialsNotFoundException().getMessage());
publish(new AuditEvent("",
AuthenticationAuditListener.AUTHENTICATION_FAILURE, data));
}
2、AuthorizationFailure Event 이벤트인 경우 Audit Event 이벤트를 보내고, type은 AUTHORIZATIONFAILURE.코드는 다음과 같습니다.
private void onAuthorizationFailureEvent(AuthorizationFailureEvent event) {
Map data = new HashMap();
data.put("type", event.getAccessDeniedException().getClass().getName());
data.put("message", event.getAccessDeniedException().getMessage());
if (event.getAuthentication().getDetails() != null) {
data.put("details", event.getAuthentication().getDetails());
}
publish(new AuditEvent(event.getAuthentication().getName(), AUTHORIZATION_FAILURE,
data));
}
자동 어셈블:
AuditAutoConfiguration에서 다음과 같은 코드로 어셈블되었습니다.
@Bean
@ConditionalOnClass(name = "org.springframework.security.access.event.AbstractAuthorizationEvent")
@ConditionalOnMissingBean(AbstractAuthorizationAuditListener.class)
public AuthorizationAuditListener authorizationAuditListener() throws Exception {
return new AuthorizationAuditListener();
}
3. 절차 분석
스프링 boot 애플리케이션에서 Audit Event의 이벤트 처리를 활성화하려면 다음과 같이 스프링-boot-starter-security 의존도를 추가해야 합니다.
org.springframework.boot
spring-boot-starter-security
의존만으로는 부족합니다. 우리는 보안 설정에 가입해야 합니다. 그렇지 않으면 Authorization Audit Listener, Authentication Audit Listener가 어떤 사건을 감청해야 합니까?따라서 다음과 같은 코드가 포함되어 있습니다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/error-log").hasAuthority("ROLE_TEST").antMatchers("/", "/home")
.permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and()
.logout().logoutUrl("/logout").permitAll().and().authorizeRequests();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
configure Global에서 메모리에 사용자 이름은 user이고 비밀번호는password이며 역할은 USER입니다.configure에서는 다음과 같이 구성했습니다.
@Controller
public class UserController {
@RequestMapping("/")
public String index() {
return "index";
}
@RequestMapping("/hello")
public String hello() {
return "hello";
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "login";
}
@RequestMapping("/error-test")
public String error() {
return "1";
}
}
src/main/resources/templates 디렉토리에 다음과 같은 페이지를 만듭니다.
hello.html, 코드는 다음과 같습니다.
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
Hello World!
Hello [[${#httpServletRequest.remoteUser}]]!
index.html, 코드는 다음과 같습니다.
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
Spring Security
Spring Security에 오신 것을 환영합니다!
login.html, 코드는 다음과 같습니다.
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
Spring Security Example
if="${param.error}">
if="${param.logout}">
사용자 이름:암호:
테스트
응용 프로그램을 시작한 후 다음과 같은 링크에 액세스합니다.http://127.0.0.1:8080/다음 페이지로 돌아갑니다.
index를 클릭하십시오.html의 하이퍼링크 후 검증이 필요하기 때문에login 페이지로 돌아갑니다. 그림:
잘못된 사용자 이름, 암호를 입력하고 다음 페이지로 돌아갑니다.
사용자, password 를 입력한 후 다음 페이지로 돌아갑니다.
로그아웃을 클릭하면 페이지가 다음과 같습니다.
지금 방문http://127.0.0.1:8080/error-test, 로그인하지 않았기 때문에 로그인 페이지로 돌아갑니다.
방문http://127.0.0.1:8080/auditevents를 반환한 결과는 다음과 같습니다.
{
events: [
{
timestamp: "2018-01-23T03:52:13+0000",
principal: "anonymousUser",
type: "AUTHORIZATION_FAILURE",
data: {
details: {
remoteAddress: "127.0.0.1",
sessionId: null
},
type: "org.springframework.security.access.AccessDeniedException",
message: "Access is denied"
}
},
{
timestamp: "2018-01-23T03:54:21+0000",
principal: "aaa",
type: "AUTHENTICATION_FAILURE",
data: {
details: {
remoteAddress: "127.0.0.1",
sessionId: "DFDB023AEEF41BBD8079EC32402CBFD8"
},
type: "org.springframework.security.authentication.BadCredentialsException",
message: "Bad credentials"
}
},
{
timestamp: "2018-01-23T03:55:50+0000",
principal: "user",
type: "AUTHENTICATION_SUCCESS",
data: {
details: {
remoteAddress: "127.0.0.1",
sessionId: "DFDB023AEEF41BBD8079EC32402CBFD8"
}
}
},
{
timestamp: "2018-01-23T03:58:38+0000",
principal: "anonymousUser",
type: "AUTHORIZATION_FAILURE",
data: {
details: {
remoteAddress: "127.0.0.1",
sessionId: "6E6E614D638B6F5EE5B7E8CF516E2534"
},
type: "org.springframework.security.access.AccessDeniedException",
message: "Access is denied"
}
},
{
timestamp: "2018-01-23T04:00:01+0000",
principal: "anonymousUser",
type: "AUTHORIZATION_FAILURE",
data: {
details: {
remoteAddress: "127.0.0.1",
sessionId: "6E6E614D638B6F5EE5B7E8CF516E2534"
},
type: "org.springframework.security.access.AccessDeniedException",
message: "Access is denied"
}
},
{
timestamp: "2018-01-23T04:00:12+0000",
principal: "user",
type: "AUTHENTICATION_SUCCESS",
data: {
details: {
remoteAddress: "127.0.0.1",
sessionId: "6E6E614D638B6F5EE5B7E8CF516E2534"
}
}
}
]
}
해석
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.