<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230227</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- DB로 세션관리-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
<!-- h2, oracle, mysql -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
@Getter
@Setter
@ToString(exclude = {"password"})
public class CustomUser extends User{
private String id; // username
private String password; //password
private Collection<GrantedAuthority> authorities; //role
private String name;
private int age;
public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public CustomUser(String username, String password, Collection<GrantedAuthority> authorities, String name, int age) {
super(username, password, authorities);
this.id = username;
this.password = password;
this.authorities = authorities;
this.name = name;
this.age = age;
}
}
CustomUser.java
@Controller
@RequestMapping(value ="/customer")
@Slf4j
@RequiredArgsConstructor
public class CustomerController {
final String format = "CustomerController => {}";
final MemberMapper mMapper;
@GetMapping(value="/home.do")
public String homeGET(
Model model,
@AuthenticationPrincipal User user,
@RequestParam(name = "menu", required = false, defaultValue = "0")int menu) {
if(menu == 1){
// 세션에서 아이디정보를 꺼내서 mapper에서 조회
Member member = mMapper.selectMemberOne1(user.getUsername());
log.info(format, member.toString());
model.addAttribute("member", member);
// 체크박스에 표시할 항목들
String[] checkLabel = {"가가가","가나다","나나나","다다다","가나다"};
model.addAttribute("checklabel", checkLabel);
}
return "/customer/home";
}
//@Authen usser user => HttpSession httpSession => httpSession.getAttribute("user")
@PostMapping(value="/home.do")
public String homePOST(@RequestParam(name="menu", required = false) int menu,
HttpServletRequest request,
HttpServletResponse response,
@ModelAttribute Member member,
@AuthenticationPrincipal CustomUser user) {
log.info("CustomerController menu => {}", user.toString());
log.info("CustomerController menu => {}", menu);
if(menu == 0){
return "redirect:home.do?menu=1";
}
if(menu == 1){
member.setId(user.getUsername());
int ret = mMapper.updateMemberOne(member);
log.info("updateMemberOne menu => {}", ret);
// 아이디 정보 가져오기 => user.getUsername();
return "redirect:/customer/home.do?menu=1";
}
else if(menu == 2){
BCryptPasswordEncoder bcpe = new BCryptPasswordEncoder();
// 비밀번호확인 => matches(바꾸기전 비번, 해시된 비번)
if(bcpe.matches("hash전비번", "hash후 비번")){
}
}
else if(menu == 3){
// 아이디 정보를 이용해서 db에서 1명 조회
// 조회된 정보와 현재암호가 일치하는지 matches로 비교
// 비교가 true이면 db에서 삭제, 로그아웃
// 컨트롤러에서 logout처리하기
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
log.info("CustomerController => {}", auth.toString());
}
return "/customer/home";
}
@GetMapping(value="/join.do")
public String joinGET() {
return "/customer/join";
}
@PostMapping(value="/join.do")
public String joinPOST(@ModelAttribute Member member) {
log.info(format, member.toString()); // 화면이 정확하게 표시되고 사용자가 입력한 항목을 member객체에 저장했음을 확인함.
BCryptPasswordEncoder bcpe = new BCryptPasswordEncoder(); // salt값을 자동으로 부여함.
member.setPassword( bcpe.encode(member.getPassword()) ); // 기존암호를 암호화시켜서 다시 저장함.
int ret = mMapper.insertMemberOne(member);
if(ret == 1){
return "redirect:joinok.do"; //주소창에 127.0.0.1:9090/ROOT/customer/joinok.do입력후 엔터키를 자동화
}
// return "redirect:home.do"; //주소창에 127.0.0.1:9090/ROOT/customer/home.do입력후 엔터키를 자동화
return "redirect:join.do"; // 실패시 회원가입화면으로
}
@GetMapping(value="/joinok.do")
public String joinokGET() {
return "/customer/joinok";
}
}
CustomerController.java
// 백엔드 Restful api 연동가능자 + mysql, oracle, mongodb ... + jpa + mybatis
// 프론트엔드 spa 싱글페이지..?
@Controller // 이게 서블렛이다..
public class HomeController {
// 127.0.0.1:9090/ROOT/home.do
@GetMapping(value = { "/home.do", "/" }) // 배열
public String homeGET(Model model, @AuthenticationPrincipal User user) {
if(user != null){ //로그인 되었음
System.out.println(user.toString());
}
model.addAttribute("user", user);
return "/home";
// request.setAttribute("key","value")
// model.addAttribute("title", "전송된타이틀");
// model.addAttribute("abc", "마음대로");
// model.addAttribute("xyz", "잠온다");
// templates/ home.html
// return "home"; // request.getRequestDispatcher("/WEB-INF/home.jsp").forward(request, response);
}
// 127.0.0.1:9090/ROOT/main.do
@GetMapping(value = "/main.do")
public String MainGET(Model model) {
model.addAttribute("title", "메인화면");
return "main"; // request.getRequestDispatcher("/WEB-INF/main.jsp").forward(request, response);
}
@GetMapping(value="/403page.do")
public String PageGET() {
return "/error/403page";
}
@GetMapping(value="/login.do")
public String loginGET() {
return "login";
}
@GetMapping(value="/logout.do")
public String logoutGET() {
return "logout";
}
}
HomeController.java
@Controller
public class WebErrorController implements ErrorController {
@GetMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if(status != null){
int statusCode = Integer.valueOf(status.toString());
if(statusCode == HttpStatus.NOT_FOUND.value()) {
return "/error/404page";
} else {
return "/error/errorpage";
}
}
return "/error/errorpage";
}
}
WebErrorController.java
@Component
public class JwtUtil {
// 토큰 생성용 보안키
private final String SECRETKEY = "dhkfkdkffkfkffkzizi";
// 정보 추출용 메소드
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = Jwts.parser().setSigningKey(SECRETKEY).parseClaimsJws(token).getBody();
return claimsResolver.apply(claims);
}
// 토큰 생성(아이디 정보를 이용한 토큰 생성)
// 아이디만포함. json -> string -> 토큰 -> string -> json
// {"uid":"aaa", "role":"CUSTOMER"}
public String generateToken(String username, String role) {
// 1. jsonobject로 변환
JSONObject jobj = new JSONObject();
jobj.put("username", username);
jobj.put("role", role);
// ex) 30분 => 1000 * 60 * 30
long tokenValidTime = 1000 * 60 * 60 * 4; // 4시간
// 2. 문자형태로 추가한 후 토큰 생성
Map<String, Object> claims = new HashMap<>();
String token = Jwts.builder().setClaims(claims)
.setSubject(jobj.toString())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + tokenValidTime))
.signWith(SignatureAlgorithm.HS256, SECRETKEY).compact();
return token;
}
// 토큰 검증
public Boolean validateToken(String token, String userid) {
// 토큰에서 아이디 정보 추출
final String username = this.extractUsername(token);
if (username.equals(userid) && !isTokenExpired(token)) {
return true;
}
return false;
}
// 토큰에서 아이디 정보 추출하기
public String extractUsername(String token) {
String sObj = extractClaim(token, Claims::getSubject);
JSONObject jobj = new JSONObject(sObj);
return jobj.getString("username");
}
// 토큰에서 권한 정보 정보 추출하기
public String extractRole(String token) {
String sObj = extractClaim(token, Claims::getSubject);
JSONObject jobj = new JSONObject(sObj);
return jobj.getString("role");
}
// 토큰에서 만료 시간 추출하기
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
// 토큰의 만료시간이 유효한지 확인
public Boolean isTokenExpired(String token) {
// 만료시간 가져와서 현재시간보다 이전인지 확인
return this.extractExpiration(token).before(new Date());
}
}
JwtUtil.java
// => html을 표시할수 없음
// Map, Member, Board, List를 반환하면 자동으로 json으로 바꿔줌
@RestController
@RequestMapping(value="/api/board")
@RequiredArgsConstructor
// GET => 조회
// POST => 추가, 로그인
// DELETE => 삭제
// PUT => 전체수정, PATCH => 일부수정
@Slf4j
public class RestBoardController {
final BoardMapper bMapper; // 매퍼객체생성
final String format = "RESTBoard => {} ";
// 게시글 삭제
@DeleteMapping(value="/delete.json")
public Map<String, Integer> deleteDelete(@RequestBody Board board){
int ret = bMapper.deleteBoardOne(board.getNo());
Map<String, Integer> retMap = new HashMap<>();
retMap.put("result", ret);
return retMap;
}
// 게시글 수정
@PutMapping(value="/update.json")
public Map<String, Integer> updatePut(@RequestBody Board board){
int ret = bMapper.updateBoardOne(board);
Map<String, Integer> retMap = new HashMap<>();
retMap.put("result", ret);
return retMap;
}
// 게시글 조회수 증가
// 게시글 번호가 전달되면 update를 이용해서 게시글조회수를 증가시키고 결과를 result:1, result:0
//127.0.0.1:9090/ROOT/api/board/updatehit.json
@RequestMapping(value = "/updatehit.json", method = {RequestMethod.PUT})
public Map<String, Integer> updatehitPUT(@RequestBody Board board){
log.info(format, board.toString());
int ret = bMapper.updateBoardHit(board.getNo());
Map<String, Integer> retMap = new HashMap<>();
retMap.put("result", ret);
return retMap;
}
//127.0.0.1:9090/ROOT/api/board/insert.json
// 게시판 글쓰기 => 제목, 내용, 작성자 =>{"title" : "a", "content" : "b", "writer" : "c"}
@RequestMapping(value = "/insert.json", method = {RequestMethod.POST})
public Map<String, Integer> insertPOST(@RequestBody Board board ){
//전송되는 값 확인
log.info(format, board.toString());
// DB에 추가하고 결과를 1또는 0으로 반환
int ret = bMapper.insertBoardOne(board);
Map<String, Integer> retMap = new HashMap<>();
retMap.put("result", ret);
return retMap;
}
//127.0.0.1:9090/ROOT/api/board/selectlist.json
@RequestMapping(value="/selectlist.json", method={RequestMethod.GET})
public List<Board> requestMethodName() {
// [{},{},{},{},{},...,{}]
return bMapper.selectBoardList();
}
//127.0.0.1:9090/ROOT/api/board/select.json
@GetMapping(value="/select.json")
public Map<String, String> selectGET() {
Map<String, String> retMap = new HashMap<>();
retMap.put("result", "ok");
return retMap;
}
}
RestBoardController.java
@RestController
@RequestMapping(value = "/api/member")
@RequiredArgsConstructor
@Slf4j
public class RestMemberController {
final JwtUtil jwtUtil; // Component 객체 생성
final MemberMapper mMapper;
BCryptPasswordEncoder pcpe = new BCryptPasswordEncoder(); // 객체생성
// 로그인 => 로그인 성공시 세션에 정보를 저장하고 다른페이지에서 확인
// 로그인 => 토큰을 발행함.(토큰에는 로그인 사용자 정보 만료시간이 포함되어 있음.)
//127.0.0.1:9090/ROOT/api/member/login.json => {"id":"cccc", "password":"a"}
@PostMapping(value = "/login.json")
public Map<String, Object> loginPOST(@RequestBody Member member){
// 아이디를 이용해서 회원정보 가져오기
Member retMember = mMapper.selectMemberOne1(member.getId());
log.info("RestMember => {}",retMember.toString());
Map<String, Object> retMap = new HashMap<>();
retMap.put("result", 0);
if(retMember != null){
if(pcpe.matches(member.getPassword(), retMember.getPassword() )){
// 암호가 일치하면 토큰 발행
retMap.put("result", 1);
retMap.put("token", jwtUtil.generateToken(member.getId(), "CUSTOMER"));
}
}
// 가져온 암호와 전달된 암호가 일치하는지 확인
return retMap;
}
// 회원가입
@PostMapping(value = "/join.json")
public Map<String, Integer> joinPOST(){
int ret =1;
Map<String, Integer> retMap = new HashMap<>();
retMap.put("result", ret);
return retMap;
}
}
RestMemberController.java
@Service
@Slf4j
@RequiredArgsConstructor
public class SecurityServiceImpl implements UserDetailsService{
final MemberMapper mMapper;
final String format = "SecurityServiceImpl => {}";
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info(format,username);
// 아이디를 전달해서 정보를 받아옴 암호까지 받아옴
// DB연동....
Member member = mMapper.selectMemberOne1(username);
if(member != null){ // 가져올 정보가 있음. 존재하는 아이디가 있음
// Member DTO를 사용해서 처리하나 시큐리티는 User DTO를 사용함
// 세션에는 User타입으로 저장됨.
// User를 이용할 경우 (세션내용 -> 아이디, 암호, 권한)
// return User.builder().username(member.getId()).password(member.getPassword()).roles(member.getRole()).build();
String[] strRole = { "ROLE_" + member.getRole() };
Collection<GrantedAuthority> role = AuthorityUtils.createAuthorityList(strRole);
return new CustomUser(member.getId(), member.getPassword(), role, member.getName(),member.getAge());
}
// 존재하지 않는 아이디
return User.builder()
.username("_")
.password("_")
.roles("_")
.build();
}
}
SecurityServiceImpl.java
@Mapper
public interface BoardMapper {
// 글쓰기
@Insert({
" Insert into board( title, content, writer)",
" values(#{obj.title},#{obj.content},#{obj.writer}) "
})
public int insertBoardOne(@Param("obj") Board obj);
// 게시글 목록 조회
@Select({
" select b.* From Board b Order By no DESC "
})
public List<Board> selectBoardList();
// 게시글 1개 조회
@Select({
" Select b.* from board b where no=#{no} "
})
public Board selectBoardOne(@Param("no") long no);
// 게시글 수정
// @Select({
// " UPDATE board SET title=#{title} WHERE NO =#{no} "
// })
// sql문이 없음 => resources/mappers/파일명Mapper.xml
public int updateBoardOne(Board obj);
// sql문이 없음 => resources/mappers/파일명Mapper.xml
public int deleteBoardOne(long no);
// // 게시글 조회수 증가
public int updateBoardHit(long no);
}
BoardMapper.java
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.BoardMapper">
<update id="updateBoardOne" parameterType="com.example.dto.Board">
UPDATE board SET title=#{title}, content=#{content}, writer=#{writer} WHERE NO =#{no}
</update>
<delete id="deleteBoardOne" parameterType="long">
DELETE FROM BOARD WHERE no=#{no}
</delete>
<update id="updateBoardHit" parameterType="Board">
UPDATE board SET hit=hit+1 WHERE NO =#{no}
</update>
</mapper>
boardMapper.xml
@Mapper
public interface MemberMapper {
public int insertMemberOne(Member member);
public Member selectMemberOne(Member member);
public Member selectMemberOne1(String userid);
public int updateMemberOne(Member member);
}
MemberMapper.java
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.MemberMapper">
<insert id="insertMemberOne" parameterType="com.example.dto.Member">
INSERT INTO MEMBER(id, password, name, age, role)
VALUES(#{id}, #{password}, #{name}, #{age}, #{role})
</insert>
<select id="selectMemberOne" parameterType="Member" resultType="Member">
SELECT m.id, m.name, m.age, m.role FROM member m WHERE m.id=#{id} AND m.password=#{password}
</select>
<select id="selectMemberOne1" parameterType="string" resultType="Member">
SELECT m.* FROM member m WHERE m.id=#{id}
</select>
<update id="updateMemberOne" parameterType="Member">
update member set name=#{name}, age=#{age} WHERE id=#{id}
</update>
</mapper>
memberMapper.xml