회고록(TIL&WIL)
TIL 2023.03.22 Spring Security, React-Redux, Scheduling
만들어나가자
2023. 3. 22. 19:20
Security를 적용한 Web MVC
ServletConfig 에서 Controller를 대신 작성
package com.bit.boot12.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class ServletConfig implements WebMvcConfigurer{
@Bean
PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
// registry.addViewController("/login").setViewName("login");
registry.addViewController("/join").setViewName("join");
// registry.addViewController("/dept").setViewName("dept/index");
}
}
SecurityConfig
package com.bit.boot12.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests)-> requests
.antMatchers("/","/join").permitAll()
.anyRequest().authenticated()
)
.formLogin((form)->form.loginPage("/login").permitAll())
.logout((logout)->logout.permitAll());
http.csrf().disable(); // csrf 체크를 해야하는데 테스트를 위해서 비사용 처리
return http.build();
}
}
UserDetailsImpl
package com.bit.boot12.service;
import java.time.LocalDate;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import lombok.Getter;
@Getter
public class UserDetailsImpl extends User {
private LocalDate hiredate;
private int mgr;
private String job;
public UserDetailsImpl(String username, String password, Collection<? extends GrantedAuthority> authorities, String job, LocalDate hiredate, int mgr) {
super(username, password, authorities);
this.job = job;
this.hiredate = hiredate;
this.mgr = mgr;
}
}
UserDetailServiceImpl
package com.bit.boot12.service;
import java.util.List;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.bit.boot12.domain.EmpRepo;
import com.bit.boot12.domain.EmpVo;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class UserDetailServiceImpl implements UserDetailsService {
final EmpRepo empRepo;
final PasswordEncoder encoder;
@Override
public UserDetails loadUserByUsername(String ename) throws UsernameNotFoundException {
EmpVo bean = empRepo.findByEname(ename).getEntity();
// return User.builder()
// .username(bean.getEname())
// .password(encoder.encode(String.valueOf(bean.getEmpno())))
// .authorities("USER")
// .build();
return new UserDetailsImpl(bean.getEname(),
encoder.encode(String.valueOf(bean.getEmpno())),
List.of(new SimpleGrantedAuthority("USER")),
bean.getJob(),
bean.getHiredate(),
bean.getMgr()
);
}
}
HomeController
package com.bit.boot12;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import com.bit.boot12.service.UserDetailsImpl;
@Controller
public class HomeController {
@GetMapping("/login")
public void login() {}
@GetMapping("/dept")
public String list(Authentication authentication, @AuthenticationPrincipal UserDetails userDetails) {
System.out.println(userDetails);
System.out.println(userDetails.getUsername());
System.out.println(((UserDetailsImpl)userDetails).getJob());
System.out.println(authentication.getPrincipal());
System.out.println(((UserDetailsImpl)authentication.getPrincipal()).getUsername());
System.out.println(((UserDetailsImpl)authentication.getPrincipal()).getJob());
SecurityContext context = SecurityContextHolder.getContext();
String name = context.getAuthentication().getName();
System.out.println(name);
return "dept/index";
}
}
thymeleaf - index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Please sign in</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet"
crossorigin="anonymous" />
</head>
<body>
<div class="container">
<nav>
<a href="/" th:href="@{/}" class="btn btn-default" role="button">Home</a>
<a href="/" th:href="@{/dept}" class="btn btn-default" role="button">List</a>
<a sec:authorize="{!isAuthenticated()}" href="/" th:href="@{/login}" class="btn btn-default" role="button">Login</a>
<a sec:authorize="{!isAuthenticated()}" href="/" th:href="@{/join}" class="btn btn-default" role="button">Join</a>
<a sec:authorize="{isAuthenticated()}" href="/" th:href="@{/logout}" class="btn btn-default" role="button">Logout</a>
</nav>
<h1>list page</h1>
Logged user: <span sec:authentication="name">Bob</span>
</div>
</body>
</html>
react-redux
프로젝트 생성 및 redux 설치
npx create-react app .
npm i redux react-redux
App3.js
import { Provider, useDispatch, useSelector } from 'react-redux';
import Comp1 from './components/Component1';
import Comp2 from './components/Component2';
import Comp3 from './components/Component3';
import Comp4 from './components/Component4';
import Comp5 from './components/Component5';
import { store } from './components/store';
export default function App() {
return (
<Provider store={store}>
<Comp3/>
<Comp4/>
<Comp5/>
</Provider>
)
}
store.js
import { createStore } from 'redux';
function reducer (prev={val:1}, action) {
console.log(prev, action)
if(action.type === 'up'){
return {...prev, val:prev.val+1};
};
if(action.type === 'upup'){
return {...prev, val:prev.val+action.su};
};
return {...prev};
}
const store = createStore(reducer);
export {store, reducer};
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App3';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
Component1.js
import { useDispatch } from "react-redux";
export default function Comp1() {
const dispatch = useDispatch();
return <div><button onClick={e=>dispatch({type:'up'})}>클릭+1</button></div>
}
Component2.js
import { useSelector } from "react-redux";
export default function Comp2() {
const su = useSelector(state=>state.val);
return <div><h1>{su}</h1></div>
}
Component3.js
import { useDispatch } from "react-redux";
export default function Comp3() {
const dispatch = useDispatch();
return <div><button onClick={e=>dispatch({type:'upup', su:10})}>클릭+10</button></div>
}
Component4.js
import Comp1 from "./Component1";
export default () => <><Comp1/></>
Component5.js
import Comp2 from "./Component2";
export default () => <><Comp2/></>
Spring Scheduling
의존성 추가
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>3.1.2</version>
<scope>test</scope>
</dependency>
@EnableScheduling
package com.bit.boot14;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class Boot14Application {
public static void main(String[] args) {
SpringApplication.run(Boot14Application.class, args);
}
}
ScheduleTasks
package com.bit.boot14.scheduleingtasks;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.debug("The time is now {}", dateFormat.format(new Date()));
}
}