실행 결과
1) boardList에서 글작성 클릭 시 로그인 창 출력
로그인을 하지 않아도 글을 보거나 게시판 목록 접근 가능하지만 글 등록은 로그인하여야 함

2) ROLE_ADMIN 권한을 갖고 있는 아이디 비밀번호로 접속

3) 게시글 작성

4) 해당 게시글 확인

5.
테이블 설계
▶member 테이블
create table member( userid varchar(50) primary key, userpw varchar(100) not null, username varchar(100) not null, regdate datetime not null default current_timestamp, updatedate datetime, enabled char(1) default '1' );
▶member_auth 테이블
create table member_auth( userid varchar(50) not null, auth varchar(50) not null, foreign key(userid) references member(userid) );
회원 도메인, 회원 Mapper 설계
회원 도메인과 회원 Mapper 부터 설계해준다.
▶Member
public class Member { private String userid; private String userpw; private String userName; private boolean enabled; private String regDate; private String updateDate; private List<Auth> authList; public String getUserid() { return userid; } public void setUserid(String userid) { this.userid = userid; } public String getUserpw() { return userpw; } public void setUserpw(String userpw) { this.userpw = userpw; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public String getRegDate() { return regDate; } public void setRegDate(String regDate) { this.regDate = regDate; } public String getUpdateDate() { return updateDate; } public void setUpdateDate(String updateDate) { this.updateDate = updateDate; } public List<Auth> getAuthList() { return authList; } public void setAuthList(List<Auth> authList) { this.authList = authList; } }
여러 개의 사용자 권한을 가질 수 있도록 authList
▶Auth
public class Auth { private String userid; private String auth; public String getUserid() { return userid; } public void setUserid(String userid) { this.userid = userid; } public String getAuth() { return auth; } public void setAuth(String auth) { this.auth = auth; } }
▶member.xml
<?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.spring.boardapp.dao.MemberDao"> <resultMap type="com.spring.boardapp.domain.Member" id="memberMap"> <id property="userid" column="userid"/> <result property="userid" column="userid"/> <result property="userpw" column="userpw"/> <result property="userName" column="username"/> <result property="regDate" column="regdate"/> <result property="updateDate" column="updatedate"/> <collection property="authList" resultMap="authMap"> </collection> </resultMap> <resultMap type="com.spring.boardapp.domain.Auth" id="authMap"> <result property="userid" column="userid"/> <result property="auth" column="auth"/> </resultMap> <select id="getAuth" resultMap="memberMap"> SELECT mem.userid, userpw, username, enabled, regdate, updatedate, auth FROM member mem LEFT OUTER JOIN member_auth auth ON mem.userid = auth.userid WHERE mem.userid = #{userid} </select> </mapper>
Member가 Auth를 갖고 있는 1:N 관계이므로 이럴 때 resultMap을 사용하는 것이 좋다.
▶MemberDaoImpl.java
MemeberDao 인터페이스는 생략하도록 하겠다.
@Repository("memberDao") public class MemberDaoImpl implements MemberDao { @Autowired SqlSession sqlSession; @Override public Member get(String userid) { // TODO Auto-generated method stub return sqlSession.selectOne("getAuth", userid); } }
BCryptPasswordEncoder 이용
스프링 시큐리티에서 제공되는 BCryptPasswordEncoder 클래스를 이용해서 패스워드를 암호화해서 처리되도록 하겠다. 참고로 스프링 시큐리티 5부터는 패스워드 암호화를 반드시 처리해주어야 한다.
BcryptPasswordEncoder는 스프링 시큐리티의 API안에 포함되어 있다.
때문에 그냥 빈을 추가해주고 PasswordEncoder를 bcryptPasswordEncoder로 지정해준다.
... <bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" /> <security:http> ... </security:http> <security:authentication-manager> <security:authentication-provider> <security:jdbc-user-service data-source-ref="dataSource"/> <security:password-encoder ref="bcryptPasswordEncoder" /> </security:authentication-provider> </security:authentication-manager> </beans>
커스텀 UserDetailsService 활용
CustomUserDetailsService 구성
- 스프링 시큐리티의 UserDetailsService를 구현
- MemberDao 타입의 인스턴스를 주입받아서 실제 기능을 구현
UserDetailsService 구성
- 추상 메소드: loadUserByUsername()
- 반환 타입: UserDetails 타입
반환 타입은 UserDetails 타입이기 때문에 우리가 적용하려는 Member 타입을 아래와 같은 구현을 통해 UserDetails 타입으로 반환해주어야 한다.
▶CustomUser.java
import java.util.Collection; import java.util.stream.Collectors; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import com.spring.boardapp.domain.Member; public class CustomUser extends User { private static final long serialVersionUID = 1L; private Member member; public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) { super(username, password, authorities); } public CustomUser(Member member) { super(member.getUserid(), member.getUserpw(), member.getAuthList().stream().map(auth -> new SimpleGrantedAuthority(auth.getAuth())) .collect(Collectors.toList())); this.member = member; } }
- User 클래스를 상속하기 때문에 부모 클래스의 생성자를 호출해야 정상적인 객체를 생성할 수 있다.
- Member를 파라미터로 전달하여 User 클래스에 맞게 생성자를 호출
- Auth 인스턴스는 GrantedAuthority 객체로 변환해야 하므로 stream()과 map()을 이용하여 처리
▶CustomUserDetailsService.java
public class CustomUserDetailsService implements UserDetailsService { @Resource(name="memberDao") private MemberDao memberDao; @Override public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { System.out.println("Load User By UserName : " + userName); // userName means userid Member member = memberDao.get(userName); System.out.println("queried by member mapper: " + member); return member == null ? null : new CustomUser(member); } }
▶security-context.xml
... <bean id="customAccessDenied" class="com.spring.boardapp.security.CustomAccessDeniedHandler"></bean> <bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" /> <bean id="customUserDetailsService" class="com.spring.boardapp.security.CustomUserDetailsService"></bean> <security:http> ... </security:http> <security:authentication-manager> <security:authentication-provider user-service-ref="customUserDetailsService"> <security:password-encoder ref="bcryptPasswordEncoder" /> </security:authentication-provider> </security:authentication-manager> </beans>
빈 설정하는 부분에 대해 궁금한 것이 있는 사람은 다음 링크를 보고 오도록 하자 [ 간단 로그인 구현 ]
로그인에 따른 권한 변화
▶BoardController.java
@RequestMapping(value = "/regist") @PreAuthorize("isAuthenticated()") //로그인한 사용자 모두 등록 가능 public String registBoard(@RequestParam Map<String, Object> paramMap, Model model,
▶boardList.jsp
<tr> <td> <sec:authorize access="isAnonymous()"> <input type="button" value="로그인" onclick="location.href='/customLogin'"> </sec:authorize> <sec:authorize access="isAuthenticated()"> <form role="form" method="post" action="/customLogout"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> <input type="submit" value="로그아웃" /> </form> </sec:authorize> </td> <td align="right"><input type="button" value="글 작성" onclick="location.href='/board/regist'"> </tr>
위와 같은 형식으로 적용하고자 하는 것에 적용
https://yeo-computerclass.tistory.com/351
[spring security] @PreAuthorize, @PostAuthorize 어노테이션 이용한 스프링 시큐리티 설정
어노테이션을 이용하여 spring security를 설정하기 위해선 설정을 해주어야 한다. 설정 XML 설정 기반인 경우 ▶servlet-context.xml Java 설정 기반인 경우 ▶ServletConfig.java @EnableWebMvc @ComponentScan(..
yeo-computerclass.tistory.com
'ETC' 카테고리의 다른 글
[springMVC + Ajax] 게시판 첨부파일 추가 (0) | 2022.09.27 |
---|---|
Spring + Ajax 파일 다운로드 시 Internet Explorer, Edge에서 한글이름 깨짐 (0) | 2022.09.26 |
Spring + Ajax 파일 다운로드 (0) | 2022.09.26 |
Spring + Ajax 섬네일 파일 생성, 이미지 파일인지 구분, 이미지 클릭 시 원본 이미지 출력 (0) | 2022.09.25 |
Spring + Ajax 중복된 파일 이름 해결 (0) | 2022.09.25 |