출처: https://bumcrush.tistory.com/182 [맑음때때로 여름]

회사에서 소소하게 프로젝트를 시작했는데
기존에 원래 되어있던게 스프링도 아닌 정말 기본 자바 프로젝트;;였다
로그인할땐 특정 uuid를 생성해서 인증key같은 걸로 사용하다가
세션이 종료 되었을때 사라지게 해달라고 요청받음..
방법을 찾다보니 HttpSessionListener를 쓰면 된다고..

 

1. web.xml에 추가하기

 

<listener>
<listener-class>egovframework.dev3.common.web.SessionListener</listener-class>
</listener>

ㅎ.. sessiontimeout은 그냥 테스트하느라 1로 했음

<session-config>
<session-timeout>1</session-timeout>
</session-config>

 

2. 맨들기

public class SessionListener implements HttpSessionListener {
@Override
public void sessionCreated(final HttpSessionEvent event) {
LocalTime now = LocalTime.now();
HttpSession session = event.getSession();
System.out.println(session.getId());
System.out.println("세션 IN : " + now);
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
LocalTime now = LocalTime.now();
HttpSession session = event.getSession();
System.out.println("세션 OUT : " + now);
}
}

 

 

3. 결과

 

그렇다고 한다.

 

https://stackoverflow.com/questions/47166425/how-to-force-javamailsenderimpl-to-use-tls1-2

 

How to force JavaMailSenderImpl to use TLS1.2?

Have a JDK7 app running on Tomcat and it does have the following env settings: -Dhttps.protocols=TLSv1.1,TLSv1.2 The above setting ensures that we don't use TLS 1.0 when connecting over HTTPS wh...

stackoverflow.com

<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.5.0-b01</version>
</dependency>
Properties prop = new Properties();
prop.setProperty("mail.smtp.auth", "true");
prop.setProperty("mail.smtp.starttls.enable", "true");
prop.setProperty("mail.smtp.ssl.protocols", "TLSv1.2"); // Added this line
prop.setProperty("mail.smtp.ssl.trust", mailUri.getHost());
mailSender.setJavaMailProperties(prop);
List<Output> rst = new ArrayList<>();
Gson gson = new GsonBuilder()
.serializeNulls()
.create();
String json = gson.toJson(rst);
System.out.println(json);

 

 

null값이 전송되지 않고 값이 있는 파라미터만 전송될때..?

사실 null값을 전송하지 말자! 하는

@JsonInclude(value = Include.NON.NULL)

어노테이션을 더 많이 사용하고 있어서

새롭게 찾았는데 안까먹으려고 메모~~~~

const requestIp = require('request-ip');
$ npm install request-ip
let Ip = requestIp.getClientIp(req)

 

- 끗 -

 

----------- 더 간단하게는 

const ip = req.headers['x-forwarded-for'] ||  req.connection.remoteAddress;

그리고 만약 로컬에서 ::1로 사용자 IP가 출력되는 경우가 있다. ::1은 IpV6에서 로컬 호스트를 의미한다. 그래서 express 서버를 생성할 때 IPV4를 사용하도록 설정하면 된다.

 


 ex) localhost는 ::1로 들어오고 다른건 ::fff:000.00.00.0. 이런식..

 

if (params.dlIp.substr(0, 7) == "::ffff:") {
params.dlIp = params.dlIp.substr(7)
}

 

 

 

 

뭔가 이렇게 바로 다운로드 시키고 싶었음

router.route('/sdk/download').get(async (req, res)=> {
logger.info('request => [get] /sdk/download');
// 필요한 작업 솰라솰라하고나서
// 다운로드
let filepath = '파일경로';
res.download(filepath)
});

 

그냥 이게 끝이었다 ^_^

 

 

라고 생각했는데 뭔가 구리게 파일이 떨어짐..

파일명 같은게 32ru3ifeiosfhsiodf923이런식으로;

 

const path = require('path');
let filepath = '파일경로 + 파일명' // ex) /dsdfdsf/dfdfd/dfdf/dd.zip
res.download(filePath, encodeURIComponent(path.basename(filePath)));

 

이렇게 해주면 파일명도 깰끔~~

'nodejs' 카테고리의 다른 글

node ip 클라이언트 ip 가져오기  (0) 2022.02.14
NODE 에서 파일 저장하기 :: FS 이용  (2) 2022.02.07
[nodejs] 데이터 받기 body-parser  (0) 2021.02.24
[nodejs] nodejs / expressjs 설치  (0) 2021.02.24

스프링에서 파일 저장할때는 그냥 multipart 를 이용해서 슝슝 저장하면 됐는데

노드의 파일 시스템은 fs... 예.. 가져와서 쓴다.

const fs = require('fs');

 

 

파일은 BASE64 STRING으로 받는다~

버퍼를 이용해서 파일 저장..

 

 

저장해따

let file = fs.readFileSync('./message.js', 'utf-8'); //message.js 파일 읽기
let encode = Buffer.from(file).toString('base64'); //파일 base64로 인코딩
let mk = fs.writeFileSync('./encodeFile', encode); // 인코딩된 파일 만들기
let file2 = fs.readFileSync('./encodeFile', 'utf-8'); // 인코딩된 파일 읽기
let decode = Buffer.from(file2, 'base64').toString('utf-8'); //파일 디코딩
let mk2 = fs.writeFileSync('./decodeFile.js', decode); //디코딩된 파일 만들기

 

구글 OAuth

https://console.cloud.google.com/

 

Google Cloud Platform

하나의 계정으로 모든 Google 서비스를 Google Cloud Platform을 사용하려면 로그인하세요.

accounts.google.com

 

1. 새 프로젝트 생성

2. 사용자 동의화면 필요한 정보 입력

3. OAuth 클라리언트 ID 만들기

3-1 웹에서 사용할 거니까 웹 애플리케이션으로 해줌

리다이렉션 URI는 라이브러리를 사용하면 따로 컨트롤러에서 mapping을 해주지 않아도

/login/oauth2/code/google로 고정이댜~ 라이브러리가 알아서 처리를 해준다!

완료하면 클라이언트 ID와 비밀번호가 뜨는데 노출되지 않도록 하자~

 

 

OAuth2.0 라이브러리 사용을 위해 의존성 추가추가!

 

https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-oauth2-clien0

<!--OAUTH-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

application.yml 파일도 수정

 

/oauth2/authorization/google 주소 역시 /login/oauth2/code/google와 같이 이 주소를 써야한다.

하지만 이거만 쓰면 냅다 404가 떠버린다

 

security config 파일에 Oauth로그인페이지 설정을 해주어야한다. 구럼 잘뜸!

.and()
.oauth2Login()
.loginPage("/loginForm"); // oauth로그인페이지

 

로그인은 잘 되지만 권한이 없는 manager 페이지 같은 경우에는 403이뜬다.

이 경우에는 구글 로그인이 완료된 뒤의 후처리가 필요하다.

[ webSecurityConfigurerAdapter를 상속한 sercurityConfig ]

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) 추가

// secured 어노테이션 활성화 & preAuthorize/PostAuthorize 어노테이션 활성화

package com.cos.security1.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity // 활성화 - 스프링 시큐리티 필터가 스프링 필터체인에 등록이 된다
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) // secured 어노테이션 활성화 & preAuthorize/PostAuthorize 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // /login으로 갈때 스프링 시큐리티가 낚아채지 않도록
http.authorizeRequests()
.antMatchers("/user/**").authenticated()
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll()// 다른주소는 OK
.and()
.formLogin()
.loginPage("/loginForm") // 로그인안햇을때 403페이지가 뜨는게 아니라 login페이지를 뜨게만들어줌
.loginProcessingUrl("/login") //login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행
.defaultSuccessUrl("/"); // 로그인이 완료되면 이동할 주소
}
@Bean // 해당 메서드의 리턴되는 오브젝트를 ioc로 등록
public BCryptPasswordEncoder encodePwd(){
// 비밀번호 암호화를 위해
return new BCryptPasswordEncoder();
}
}

[ Contoller ]

postAuthorize는 잘 사용하지 않는다.

실행시점?의 차이. 

@Secured("ROLE_ADMIN")
@GetMapping("/info")
public @ResponseBody String info(){
return "개인정보";
}
@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
// @PostAuthorize()
@GetMapping("/data")
public @ResponseBody String data(){
return "데이터정보";
}

로그인폼과 회원가입폼 HTML

더보기

로그인폼과 회원가입폼

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지</h1>
<hr>
<form action="/login" method="POST">
<input type="text" name="username" placeholder="UserName"/><br/>
<input type="password" name="password" placeholder="Password"/><br/>
<button>로그인</button>
</form>
<a href = "/joinForm">회원가입을 아직 않으셨나요?</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>회원가입 페이지</title>
</head>
<body>
<h1>회원가입 페이지</h1>
<form action="/join" method="POST">
<input type="text" name="username" placeholder="UserName"/><br/>
<input type="password" name="password" placeholder="Password"/><br/>
<input type="email" name="email" placeholder="Email"/><br/>
<button>회원가입</button>
</form>
</body>
</html>

 

[ 회원가입 Contoller ]

@PostMapping("/join")
public String join(User user){
System.out.println(user);
// 2. 패스워드 암호화를 추가해줌 securityConfig에도 BcryptpasswordEncoder를 @Bean으로 등록
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword);
// 1. userRepository.save(user); // 회원가입 잘됨. 비밀번호 : 1234 => 시큐리티로 로그인을 할수 없음,
// :: 패스워드가 암호화가 안되었기 때문
user.setRole("ROLE_USER");
userRepository.save(user);
return "redirect:/loginForm";
}

[ USER MODEL ]

package com.cos.security1.model;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.sql.Timestamp;
@Entity
@Data
public class User {
@Id // primary Key
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String email;
private String role; // ROLE_USEr, ROLE_ADMIN
@CreationTimestamp
private Timestamp createdate;
}

 

[ USER REPORSITORY - JPA ]

package com.cos.security1.repository;
import com.cos.security1.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
// CRUD 함수를 JPArepository가 들고 있음
// @Repository라는 어노테이션이 없어도 Ioc가 됨 (자동 빈등록)
// -> JpaRepository를 상속했기 때문
public interface UserRepository extends JpaRepository<User, Integer> {
// findBy규칙 -> username 문법
// seelct * from user where username = #{username}
public User findByUsername(String username);
}

 

[ config 수정 ]

밑 두줄 추가.

 

public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // /login으로 갈때 스프링 시큐리티가 낚아채지 않도록
http.authorizeRequests()
.antMatchers("/user/**").authenticated()
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll()// 다른주소는 OK
.and()
.formLogin()
.loginPage("/loginForm") // 로그인안햇을때 403페이지가 뜨는게 아니라 login페이지를 뜨게만들어줌
.loginProcessingUrl("/login") //login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행
.defaultSuccessUrl("/"); // 로그인이 완료되면 이동할 주소
}

 

 

[ userDetails를 상속하는 클래스 생성 ]

유저정보를 저장.

package com.cos.security1.config.auth;
// 시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행시킴
// 로그인 진행이 완료되면 seisson을 만들어줌 (Security ContextHolder)
// 세션에 들어갈 수 있는 정보(오브젝트) > authentication 타입의 객체
// authentication 안에 User정보가 있어야 함.
// user 오브젝트 타입 > UserDetails 타입 객체
// Security Session => Authentication => UserDetails(PrincipalDetails)
import com.cos.security1.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
public class PrincipalDetails implements UserDetails {
private User user; // 콤포지션
public PrincipalDetails(User user){
this.user = user;
}
// 해당 user의 권한을 리턴하는 곳
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// 계정 만료
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정 잠김
@Override
public boolean isAccountNonLocked() {
return true;
}
// 계정 유효기간
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정 정지
@Override
public boolean isEnabled() {
// ex) 휴면계정
// 현재시간 - 로그인시간 => 1년초과하면 return false
return true;
}
}

 

[ userDetailsService를 상속한 클래스 만들기 ]

이름 그대로 유저의 정보를 저장해주는 서비스

// 시큐리티 설정에서 loginProcessingUrl("/login")
// /login 요청이 오면 자동으로 UserDetailsService 타입으로 ioc되어있는 loadUserByUsername가 함수 실행
package com.cos.security1.config.auth;
import com.cos.security1.model.User;
import com.cos.security1.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
// 시큐리티 설정에서 loginProcessingUrl("/login")
// /login 요청이 오면 자동으로 UserDetailsService 타입으로 ioc되어있는 loadUserByUsername가 함수 실행
@Service
public class PrincaipalDetailsService implements UserDetailsService{
@Autowired
private UserRepository userRepository;
// 시큐리티 session > authentication > userDetails(principalDetails)
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// html에 꼭 "username"으로 해야함 (String username)과 매칭이 안됨
// 바꾸고 싶다면 security config에 .usernameParameter("username2")
// 그냥 username을 쓰자 ^^
User userEntity = userRepository.findByUsername(username);
if(userEntity != null){
return new PrincipalDetails(userEntity);
}
return null;
}
}

[ userRepository 에는 유저의 정보를 조회하는 부분 추가 ]

// findBy규칙 -> username 문법
// seelct * from user where username = #{username}
public User findByUsername(String username);

 

spring-boot-starter-security 의존성 주입 

 

 

[ controller ]

package com.cos.security1.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class indexController {
@GetMapping({"","/"})
public String index(){
// mustache 머스태치
// 머스태치 기본폴더 src/main/resources/
// viewResolver 설정 : templates (prefix), mustache (suffix)
return "index"; // src/main/resources/templates/index.mustache
}
@GetMapping("/user")
public @ResponseBody String user(){
return "user";
}
@GetMapping("/admin")
public @ResponseBody String admin(){
return "admin";
}
@GetMapping("/manager")
public @ResponseBody String manager(){
return "manager";
}
// 스프링시큐리티가 해당주소를 낚아채버림
// SecurityConfig 파일에서 설정으로 낚아채지 않도록 !
@GetMapping("/login")
public @ResponseBody String login(){
return "login";
}
@GetMapping("/join")
public @ResponseBody String join(){
return "join";
}
@GetMapping("/joinProc")
public @ResponseBody String joinProc(){
return "회원가입 완료됨!";
}
}

 

[ config ] 

package com.cos.security1.config;
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity // 활성화 - 스프링 시큐리티 필터가 스프링 필터체인에 등록이 된다.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // /login으로 갈때 스프링 시큐리티가 낚아채지 않도록
http.authorizeRequests()
.antMatchers("/user/**").authenticated()
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll()// 다른주소는 OK
.and()
.formLogin()
.loginPage("/login"); // 권한이 없다면 403페이지가 뜨는게 아니라 login페이지를 뜨게만들어줌
}
}

 

[ webMvcConfiguer ]

- 머스태치 설정

- view 주소 설정

package com.cos.security1.config;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
MustacheViewResolver resolver = new MustacheViewResolver();
resolver.setCharset("UTF-8");
resolver.setContentType("text/html; charset=UTF-8");
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html"); // .mustache가 아닌 .html로 바꿔주기 위한 작업
registry.viewResolver(resolver); // viewResolver 등록
}
}

+ Recent posts