인프런 백기선님의 스프링 시큐리티 강의를 듣고 제가 나중에 보려고 정리해 놓습니다.
인프런 백기선의 스프링 시큐리티
principal이 있다면 -> model.addAttribute
/dashboard는 로그인 페이지로 리다이렉트 -> WebSecurityConfig에서 설정
http.fromLogin();
http.httpBasic();
@Controller
public class SampleController {
@GetMapping("/")
public String index(Model model, Principal principal) {
if(principal == null) {
model.addAttribute("message","Hello Spring Security"); //key-value
}
else {
model.addAttribute("message","Hello" + principal.getName()); //key-value
}
return "index";
}
@GetMapping("/info")
public String info(Model model) {
model.addAttribute("message","Info page"); //key-value
return "info";
}
@GetMapping("/dashboard") //로그인을 한 사용자만 접근이 가능하기 때문에 Principal이 있어야함.
public String dashboard(Model model, Principal principal) {
model.addAttribute("message","Hello" + principal.getName()); //key-value
return "dashboard";
}
@GetMapping("/admin")
public String admin(Model model, Principal principal) {
model.addAttribute("message","Hello Admin" + principal.getName()); //key-value
return "admin";
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
정리
스프링 부트의 도움을 받아 의존성을 추가할 수 있다.
@Configuration
@EnableWebSecurity //이게 나중에 뭣들인지 나중에 설명해드릴게유
public class SecurityCionfig extends WebSecurityConfigurerAdapter {
@Override //이런 식으로 오버라이딩 해서 설정함.
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/","info").permitAll()
.mvcMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and().formLogin() // and로 메서드 체이닝을 안해도됨.
.and()
.httpBasic();
/*
and에서 끊고
http.formLogin();
http.httpBasic();
해도 됨.
*/
}
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
//쉽게 이 값들이 디비에 들어가는 값. 디비에 드어갈 때는 암호화를 하잖아? 그떄 이 앞에 어떤 방법으로 암호화를 하는지 적어놓는게 noop. 여기서 noops는 암호화 하지 않았다는 의미.
.withUser("junho").password("{noop}123").roles("USER").and() //noop는 스프링 시큐리티 5부터 사용되는 암호화 인코딩 방식. 입력받은 값을 prefix에 따라서 인코딩한다.
.withUser("admin").password("{noop}!@#").roles("ADMIN");
//선언한 방식으로 인코딩을 해서 확인을 해줄 수 있음.
}
@Entity
public class Account {
@Id @GeneratedValue
private Integer id;
@Column(unique = true)
private String username;
private String password;
private String role;
/*
getter, setter
setter가 있어야 @ModelAttribute 사용 가능
*/
/*
스프링 시큐리티에는 비밀번호에 정해진 포멧이 있음. 패스워드 인코더를 빈으로 등록해서 아래와 같이 사용. {bcrypt}비밀번호 이런 식으로 사용됨. 디폴트가 비크립트 방식. sha256도 사용 가능함.
*/
public void encodePassword(PasswordEncoder passwordEncoder) {
//이것도 패스워드 인코더가 이미 있음.
this.password = passwordEncoder.encode(this.password);
}
}
/*
스프링 jpa가 해주는, 인터페이스만 만들어도 구현체를 빈으로 등록해줌
리퍼지토리만 만들었지만, 어카운트 리펕지토리라는 빈이 있고 구현체가 등록되어 있는 것 처럼 사용
*/
public interface AccountRepository extends JpaRepository<Account, Integer> {
Account findByUsername(String username);
}
@RestController //레스트 컨트롤러니까 제이슨으로 볼 수 있겠지.
public class AccountController {
@Autowired
AccountService accountService;
@GetMapping("/account/{role}/{username}/{password}")
//url 에 있는 세개의 키워드를 받아서 account 객체로 바운딩
public Account creasteAccount(@ModelAttribute Account account) {
return accountService.createNew(account);
//바인딩 된 값을 저장.
//return accountRepository.save(account);
}
}
/*
account 관련 비즈니스 로직을 처리하는 구현체
그중 하나가 유저디테일서비스의 역할을 하게 될 것입니다.
하나의 역할만 있으면 단일책임 원칙에 의해서, 하지만 이 일도 어카운트 서비스
일부라고 생각하기 때문에.
*/
/*
UserDetailsService는 authentication을 관리할 때, DAO 인터페이스를 통해서 저장소에 들어있는 유저정보를 가져오고 인증할 때 사용하는 인터페이스
물론 이 구현체를 쓴다고 해서 모든 데이터가 관계형 디비에 들어있어야 한다거나 하는 건 아님. 구현을 어떻게 할 것인지는 순전히 우리의 책임
*/
@Service
public class AccountService implements UserDetailsService {
@Autowired
PasswordEncoder passwordEncoder;
@Autowired AccountRepository accountRepository;
//유저네임을 받아와서 유저네임에 해당하는 유저 정보를 데이터에서 가져와서 유저 디테일즈라는 타입으로 리턴해줘야 함.
@Override
public UserDetails loadUserByUsername(String usernamae) throws UsernameNotFoundException {
//스프링 시큐리티는 양식이 필요함.
//일단 꺼내오고
Account account = accountRepository.findByUsername(usernamae);
if(account == null) {
throw new UsernameNotFoundException(usernamae);
}
//스프링 시큐리는 유저라는 클래스를 제공, 유저 클래스의 빌드를 이용하면 유저 디테일이라는 타입의 객체를 만들 수 있음.
//이게 없던 시절에는 유저 디테일즈 타입의 클래스를 구현해서, Account <-> 유저디테일서비스 의 어뎁터를 하는 클래스를 구현하면 됨.
return User.builder()
.username(account.getUsername())
.password(account.getPassword())
.roles(account.getRole())
.build();
}
public Account createNew(Account account) {
account.encodePassword(passwordEncoder);
return this.accountRepository.save(account);
}
}
@Configuration
@EnableWebSecurity //이게 나중에 뭣들인지 나중에 설명해드릴게유
public class SecurityCionfig extends WebSecurityConfigurerAdapter {
@Autowired
AccountService accountService;
@Override //이런 식으로 오버라이딩 해서 설정함.
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/","/info","/account/**").permitAll()
.mvcMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and().formLogin() // and로 메서드 체이닝을 안해도됨.
.and()
.httpBasic();
/*
and에서 끊고
http.formLogin();
http.httpBasic();
해도 됨.
*/
}
/*
인메모리는 필요없음.
*/
@Override
//명시적으로 알려주는 거기는 한데 안해도 되기는 함.
//하지만 유저 디테일 서비스의 구현체가 빈으로 등록되어 있기만 해도, 알아서 써줌.
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//여기다가 우리가 사용하는 유저 디테일 서ㅏ비스가 익서다. 어카운트 서비스도 유저 디테일 서비스의 구현체니까.
auth.userDetailsService(accountService);
}
}
@SpringBootApplication
public class DemoApplication {
//이걸 이렇게 바꿔주기만 하면 패스워드 인코딩을 할 수 있음. 서비스에서 인코딩할 때 쓰면 됨.
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
//이게 다양한 패스워드 인코딩을 지원함.
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
스프링 시큐리티 테스트.