반응형

다이어그램에서 보듯 한 유저는 여러 board를 가질 수 있고 여러 board는 한 user에 속한다.

외래키 제약조건을 건다 - user_id 컬럼을 사용자 테이블의 id값과 연결되도록 저장

@Entity //db연동을 위한 모델클래스임을 명시
@Data
public class Board {
   ...

    @ManyToOne
    @JoinColumn(name = "user_id", referencedColumnName = "id")
    private User user;

}

게시글 입장에선 many to one 관계
- @JoinColumn: 어떤 컬럼과 user 테이블이 연결될지를 설정한다. 
- referencedColumnName = "id" : user 테이블의 id와 조인된다. 생략가능

== 글 작성시 사용자 정보도 넣기 ==

BoardController.java

   @PostMapping("/form")
    public String postForm(@Valid Board board, BindingResult bindingResult, Authentication authentication){
        boardValidator.validate(board, bindingResult);
        if(bindingResult.hasErrors()) {
            return "board/form";
        }

        String username = authentication.getName();
        boardService.save(username, board);
        return "redirect:/board/list";
    }

-Authentication 을 파라미터로 선언해주면 인증정보가 알아서 담긴다. 
- getName() 으로 구한 유저 이름을 board 정보와 함께 boardService에 전달

@Service
public class BoardService {

    @Autowired
    private BoardRepository boardRepository;

    @Autowired
    private UserRepository userRepository;

    public Board save(String username, Board board){
        //1. 넘겨받은 username을 가지고 user의 id 조회

        User user = userRepository.findByUsername(username);
        board.setUser(user);

        return boardRepository.save(board);

    }
}

BoardService에서는 컨트롤러에서 넘겨받은 username을 가지고 user 정보를 조회한다.
-> 그러기위해선  UserRepository 에 관련 메소드를 만들어줘야한다. 

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username); //컬럼이름에 일치하는 사용자 데이터를 가져온다.
}

 

@Entity //db연동을 위한 모델클래스임을 명시
@Data
public class User {

    ...
    @OneToMany(mappedBy = "user")
    private List<Board> boards = new ArrayList<>();

}

- mappedBy 로 Board 클래스의 user를 적어준다.
- onetomany, manytoone 관계에서 보통 many쪽  에서 소유하는쪽을 @JoinColumn을 써서 적어준다

반응형

Spring Security 사용하기 위해 다음의 의존성 추가가 필요하다. (gradle 기준)

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'

 

이후 WebSecurityConfigurerAdapter을 상속받은 WebSecurityConfig클래스를 만들어준다.
*@Configuration 을 선언한 클래스에선 @Bean을 관리할 수 있다.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //application.properties의 dataSource가져옴
    @Autowired
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .antMatchers("/", "/account/register", "/css/**").permitAll() //누구나 접근할수 있는 url
                    .anyRequest().authenticated() //어떤 요청이라도 authenticated 돼야한다.
                    .and()
                .formLogin() //authenticated안된 상태에서 다른 페이지 가려하면
                    .loginPage("/account/login") //login페이지로 redirect된다.
                    .permitAll() //login 페이지는 누구라도 접근가능
                    .and()
                .logout()
                    .permitAll();
    }

}

  

테이블 ERD는 다음과 같다.
user 테이블에는 id, username, password, enabled(활성화여부)
role 테이블은 id, name(권한명)
user와 role은 many-to-many 관계이므로 
user_role 테이블을 만들어서 관계형 테이블을 만든다.

user_id 와 role_id는 각각 pk이다.
외래키 설정을 통해 무결성을 보장하도록 한다. 

 

JDBC Authentication 설정을 위해
https://www.baeldung.com/spring-security-jdbc-authentication 을 참고했다.
-PasswordEncoder: 추후 회원가입시 암호화시켜주는 bean

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //application.properties의 dataSource가져옴
    @Autowired
    private DataSource dataSource;

  ...

    //테이블에 쿼리 날림
    //스프링 datasource 가져와서 알아서 내부에서 인증처리
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth)
            throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource)
                .passwordEncoder(passwordEncoder()) //알아서 비번 암호화
                .usersByUsernameQuery ("select username,password,enabled "  //인증처리
                        + "from user "
                        + "where username = ?")
                .authoritiesByUsernameQuery("select u.username, r.name " //권한처리
                        + "from user_role ur inner join user u on ur.user_id =u.id "
                        +  "inner join role r on ur.role_id = r.id "
                        + "where u.username = ?");
    }
    
    @Bean
    public static PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

- authoritiesByUsernameQuery: 권한정보 가져오는 쿼리, user_role에는 user와 role에 대한 정보가 없다. 따라서 user테이블과 role 테이블을 조인해서 정보를 가져오도록 한다.  

select u.username, r.name 
from user_role ur 
inner join user u on ur.user_id =u.id 
inner join role r on ur.role_id = r.id 
where u.username = ?

 

==로그인 및 회원가입 컨트롤러 설정==

@Controller
@RequestMapping("/account")
public class AccountController {

    @Autowired
    private UserService userService;

    @GetMapping("/login")
    public String login(){
        return "account/login";
    }

    @GetMapping("/register")
    public String register(){
        return "account/register";
    }

    @PostMapping("/register")
    public String register(User user){ //modelattribute 생략해도 괜춘
        userService.save(user);
        return "redirect:/";
    }
}

==로그인시 에러처리==

login.html

<form class="form-signin" th:action="@{/account/login}" method="post">
   
   <h1 class="h3 mb-3 font-weight-normal"><a th:href="@{/}">Please sign in</a></h1>
        
        <div th:if="${param.error}" class="alert alert-danger" role="alert">
            Invalid username and password.
        </div>
        <div th:if="${param.logout}" class="alert alert-primary" role="alert">
            You have been logged out.
        </div>
        
    <label for="username" class="sr-only">Email address</label>
    <input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus>
    
    <label for="inputPassword" class="sr-only" name="password">Password</label>
    <input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" required>
   
    ...
</form>

- form 태그 안에서 login으로 post 요청 보내는데, 만약 에러가 발생하면(파라미터에 error발생하면) 해당 에러 메시지가 나온다(로그아웃 시에도 동일) 

 

==User, Role 모델을 만들어 many-to-many 관계설정-

참조: https://www.baeldung.com/jpa-many-to-many

@Entity //db연동을 위한 모델클래스임을 명시
@Data
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //Autoincrement 설정, IDENTITY가 많이 사용
    private Long id;

    private String username;
    private String password;
    private Boolean enabled;

    //user에 해당하는 권한이 알아서 조회돼서, roles에 담긴다.
    @ManyToMany
    @JoinTable(
            name = "user_role",
            joinColumns = @JoinColumn(name="user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private List<Role> roles = new ArrayList<>();
}

 

@Entity //db연동을 위한 모델클래스임을 명시
@Data
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //Autoincrement 설정, IDENTITY가 많이 사용
    private Long id;

    private String name; //권한명

    @ManyToMany(mappedBy = "roles") //mappedBy: User클래스에 있는 컬럼명이 된다.
    private List<User> users;

}

 

==UserRepository 생성==

public interface UserRepository extends JpaRepository<User, Long> {
}

==UserService 생성==

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    //유저 저장
    public User save(User user){
        // 1.비밀번호 인코딩
        String encodedPassword = passwordEncoder.encode(user.getPassword());
        user.setPassword(encodedPassword);

        // 2.회원 활성화 여부 - 기본적으로 enabled로 설정
        user.setEnabled(true);

        // 3. role정보 추가
        Role role = new Role();
        role.setId(1L);
        user.getRoles().add(role);
        return userRepository.save(user);
    }
}

- DI를 통해 UserRepository 객체를 받아온다.
- WebSecurityConfig에 Bean으로 있는 PasswordEncoder를 DI로 가져와서 사용자가 전달한 비밀번호 인코딩한다.

== 로그인 여부에 따른 메뉴 구성 ==
https://www.thymeleaf.org/doc/articles/springsecurity.html
참고

common.html

<a class="btn btn-secondary my-2 mr-2 my-sm-0" th:href="@{/account/login}"
   sec:authorize="!isAuthenticated()">로그인</a>
<a class="btn btn-secondary my-2 my-sm-0" th:href="@{/account/register}"
   sec:authorize="!isAuthenticated()">회원가입</a>

<form class="form-inline my-2 my-lg-0"  th:action="@{/logout}" method="post" sec:authorize="isAuthenticated()">
    <span class="text-white" sec:authentication="name">사용자</span>
    <span class="text-white mx-2" sec:authentication="principal.authorities">권한</span>
    <button class="btn btn-secondary my-2 my-sm-0" type="submit">로그아웃</button>
</form>

sec:authorize="!isAuthenticated() : isAuthenticated()은 로그인 되었을때니까 ! 붙여준다.
sec:authentication="name" : 로그인 한 사용자의 name 조회
sec:authentication="principal.authorities" :로그인 한 사용자의 권한 조회

이 기능을 추가하기 위해선 다음 의존성을 먼저 추가해주고, 

implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

네임스페이스도 추가해준다.

반응형

브랜치 생성
git branch 브랜치명

브랜치 목록 확인
git branch

특정 브랜치로 이동
git switch 브랜치명
* checkout 명령어가 git 2.23버전부터 switch, restore로 분리

브랜치 생성과 동시에 이동하기
git switch -c 브랜치명
* 기존의 git checkout -b 브랜치명 대체

브랜치 삭제하기
git branch -d 삭제할 브랜치명
* 지울 브랜치에 다른 브랜치로 적용되지 않은 내용의 커밋이 있을 시에는 -D 옵션으로 강제 삭제한다
ex) git branch -D 브랜치명

 

git commit -am : add 와 commit을 동시에, 하지만  새로운 파일 생겼을땐 하지 않는다. 

 

반응형

N개의 문자열 데이터를 입력받아 앞에서 읽을 때나 뒤에서 읽을 때나 같은 경우(회문 문자열)
이면 YES를 출력하고 회문 문자열이 아니면 NO를 출력하는 프로그램을 작성한다.
단 회문을 검사할 때 대소문자를 구분하지 않습니다.

▣ 입력설명
첫 줄에 정수 N(1<=N<=20)이 주어지고, 그 다음 줄부터 N개의 단어가 입력된다.
각 단어의 길이는 100을 넘지 않는다.

▣ 출력설명
각 줄에 해당 문자열의 결과를 YES 또는 NO로 출력한다.

▣ 입력예제 1
5
level
moon
abcba
soon
gooG

▣ 출력예제 1
#1 YES
#2 NO
#3 YES
#4 NO
#5 YES


내 풀이

import sys
sys.stdin=open("input.txt", "rt")

n = int(input())
for i in range(n):
    a = list(input().lower())
    
    lenA = len(a) #리스트 길이
    count =0
    for j in range(lenA // 2 ):
        if a[j] == a[lenA-1-j]:
            continue
        else:
            count+=1
    if count ==0:
        print('#',i+1, ' YES')
    else:
        print('#',i+1, ' NO')

설명:
- 입력값 n의 크기 만큼 반복문을 돈다. 반복하면서 입력값 문자열 a를 소문자 형태로 바꾸고 리스트로 바꿔준다.
- lenA를 통해 리스트의 길이를 구해준다.
- 변수 count를 선언한다. 여기서 카운트란 앞 뒤 수가 다른 경우의 수를 말한다.
- 앞 뒤 숫자를 비교하기때문에 반복분은 리스트 길이의 절반, 즉 2로 나눈 몫 횟수로 돌려준다.
- 반복문을 돌며 j번째 인덱스 값과 리스트길이-1-j 의 인덱스 값을 비교해준다.
- 만약 앞, 뒤 값이 같으면 continue해서 다음 앞뒤를 비교해주고, 다르면 count에 1을 추가해준다.
- 결과적으로 count가 0이면 앞뒤가 모두 같으므로 YES를 0이 아니면 NO를 출력해준다.

다른 풀이

import sys
sys.stdin=open("input.txt", "rt")

n = int(input())
for i in range(n):
    s = input().upper()
    size = len(s)

    for j in range(size//2):
        if s[j] != s[-1-j]:                       
            print('#%d NO' %(i+1))
            break
        print('#%d YES' %(i+1))

설명
- 전반적으로 내 풀이와 크게 다르진 않다. 
- 다만 2중 for문을 돌때 인덱스 값 세팅을 다르게했다. ex) s[-1]: 뒤에서부터 첫번째 인덱스값
- 또 굳이 리스트로 만들지 않고 문자열의 인덱스 값으로 바로 비교를 했다. (코드수를 줄일 수 있어 더 경제적이다.)

반응형

1에서부터 6까지의 눈을 가진 3개의 주사위를 던져서 다음과 같은 규칙에 따라 상금을 받는 게
임이 있다.


규칙(1) 같은 눈이 3개가 나오면 10,000원+(같은 눈)*1,000원의 상금을 받게 된다.
규칙(2) 같은 눈이 2개만 나오는 경우에는 1,000원+(같은 눈)*100원의 상금을 받게 된다.
규칙(3) 모두 다른 눈이 나오는 경우에는 (그 중 가장 큰 눈)*100원의 상금을 받게 된다.

예를 들어, 3개의 눈 3, 3, 6이 주어지면 상금은 1,000+3*100으로 계산되어 1,300원을 받게 된
다. 또 3개의 눈이 2, 2, 2로 주어지면 10,000+2*1,000 으로 계산되어 12,000원을 받게 된다.
3개의 눈이 6, 2, 5로 주어지면 그 중 가장 큰 값이 6이므로 6*100으로 계산되어 600원을 상금
으로 받게 된다.

N 명이 주사위 게임에 참여하였을 때, 가장 많은 상금을 받은 사람의 상금을 출력하는 프로그램
을 작성하시오

▣ 입력설명
첫째 줄에는 참여하는 사람 수 N(2<=N<=1,000)이 주어지고 그 다음 줄부터 N개의 줄에 사람
들이 주사위를 던진 3개의 눈이 빈칸을 사이에 두고 각각 주어진다.

▣ 출력설명
첫째 줄에 가장 많은 상금을 받은 사람의 상금을 출력한다.

▣ 입력예제 1
3
3 3 6
2 2 2
6 2 5

▣ 출력예제 1
12000


내 풀이

import sys

n = int(input())

maxPrice = 0
price = 0

for _ in range(n):    
    a = list(map(int, input().split())) #주사위 눈 3개 가져오기  
    if a[0] == a[1] == a[2]:
        price = 10000 + a[0]*1000
    elif a[0] == a[1] != a[2]:
        price = 1000 + a[0]*100
    elif a[1] == a[2] != a[0]:
        price = 1000 + a[0]*100
    elif a[0] == a[2] != a[1]:
        price = 1000 + a[0]*100
    else:
        price = max(a)*100
        
    if price > maxPrice:
        maxPrice = price   
            
        
print(maxPrice)

 

다른 풀이

import sys
sys.stdin=open("input.txt", "rt")

n = int(input())
res=0
for i in range(n):
    tmp = input().split()
    tmp.sort() #문자열 오름차순
    a,b,c = map(int, tmp) 

    if a==b and b==c: #3개 모두 같을때
        money=10000+a*1000
    elif a==b or a==c: #2개가 같을때
        money=1000+(a*100)
    elif b==c: #2개가 같을때
        money=1000+(b*100)
    else:
        money=c*100 #다 다를때 - 가장 큰 c기준
    if money>res:
        res=money

print(res)

 

+ Recent posts