iT邦幫忙

DAY 23
0

無痛學習SpringMVC與Spring Security系列 第 23

[Security] 自訂驗證(Customize Authentication)使用UserDetailsService(II)

昨天有一個小錯誤,就是如果我們已經實作UserDetailsService,則Repository部分則不需要重複coding且無須UserDetailsManager,因為已經有UserDetailsService可以取得UserDetails

今天首先先更新SecurityConfig,將原本的inMemoryAuthentication更換為UserDetailsService,其code如下

@EnableWebSecurity //Enable springFliterChain
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Autowired
	private DneUserDetailsService dneUserDetailsService;
	
        ......

	@Override
	@Autowired
	protected void configure(AuthenticationManagerBuilder auth)
			throws Exception {
		// TODO Auto-generated method stub
		auth //Builder Design Pattern
			.userDetailsService(dneUserDetailsService); //換成userDetailsService
	}
	
}

接著新增註冊新使用者的form class,命名為RegisterForm,其實也可以用DneUser來替代,只是每個人選擇不一樣,若使用DneUser並假設很多欄位並未在註冊表單中使用到,則可能需要於Controller新增一方法帶有WebDataBinder參數來設定那些欄位允許使用(whitelist)或是那些欄位不可使用(blacklist),有其安全性考量,詳細有文章有說明兩個的利弊,anyway,該form class code如下

import org.hibernate.validator.constraints.NotEmpty;

public class RegisterForm {
	@NotEmpty(message="First Name is required")
    private String firstName;
    @NotEmpty(message="Last Name is required")
    private String lastName;
    @NotEmpty(message="Username is required")
    private String username;
    @NotEmpty(message="Password is required")
    private String password;
     
     ....getter and setter
}

這裡有用到Hibernate Vaildator的Annotation,主要是宣告該欄位不可為空白,當然還有其他的annotation,可詳相關文件,請加入dependency於pom.xml

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-validator</artifactId>
	<version>5.1.2.Final</version>
</dependency>

下一步我們需要新增一個class來取得已經登入的使用者資訊或是將告訴Spring某使用者已經登入,首先建立UserContext介面,接著新增SpringSecurityUserContext實作UserContext,其code如下:

UserContext.java

public interface UserContext {
	DneUser getCurrentUser();
	void setCurrentUser(DneUser user);
}






SpringSecurityUserContext.java

@Component
public class SpringSecurityUserContext implements UserContext{
	
	@Autowired
	private DneUserRepository userRepository;
	
	@Autowired
	private DneUserDetailsService dneUserDetailService;
	
	@Override
	public DneUser getCurrentUser() { //取得已登入使用者物件
		// TODO Auto-generated method stub
		SecurityContext context=SecurityContextHolder.getContext(); //從ContextHolder中取得SecurityContext物件
		Authentication auth=context.getAuthentication(); //從SecurityContext物件取得Authentication物件
		//檢查是否有使用者登入
		if(auth== null){
			return null;
		}
		//取得通過驗證的使用者名稱
		String username=auth.getName(); 
		return userRepository.findByUserName(username); 
	}
	@Override
	public void setCurrentUser(DneUser user) { //將使用者設定已登入
		// TODO Auto-generated method stub
		//透過Service取得UserDetails
		UserDetails userDetails=dneUserDetailService.loadUserByUsername(user.getUsername());
		//將使用者放入Authentication物件,代表已通過驗證
		Authentication auth=new UsernamePasswordAuthenticationToken(userDetails, 
				user.getPassword(), userDetails.getAuthorities());
		//將Authentication物件放入SecurityContext存放
		SecurityContextHolder.getContext().setAuthentication(auth); 
	}
}

setCurrentUser用在當新的使用者註冊完成後,就登入系統。

到目前為止,我們還缺一個Controller處理使用著註冊及註冊表單網頁,新增RegisterController,其code如下:

@Controller
public class RegisterController {
	
	@Autowired
	private UserContext userContext;
	@Autowired
	private DneUserRepository dneUserRepository;
	
	@RequestMapping(value="/register", method=RequestMethod.GET)
	public String register(@ModelAttribute RegisterForm registerForm){
		return "registerForm";
	}
	
	@RequestMapping(value="/register", method=RequestMethod.POST)
	public String processRegister(@Valid @ModelAttribute RegisterForm registerForm
			, BindingResult result){
		if(result.hasErrors()){
			return "registerForm"; //如果有欄位是空白則回到註冊表單網頁
		}
		String username=registerForm.getUsername();
		if(dneUserRepository.findByUserName(username)!=null){
			result.rejectValue("username", "errors.register.username", "The username is already in use");
			return "registerForm";  //如果有相同使用者,則回到註冊表單網頁並顯示錯誤訊息
		}
		DneUser user= new DneUser();
		user.setUsername(registerForm.getUsername());
		user.setPassword(registerForm.getPassword());
		user.setFirstName(registerForm.getFirstName());
		user.setLastName(registerForm.getLastName());
		dneUserRepository.save(user); //新增使用者到資料庫
		userContext.setCurrentUser(user); //設定使用者為已登入
		return "redirect:/";
	}
}

registerForm.jsp code如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>



<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Register A New User</title>


	<div class="grid">
		<div class="row">
			<div class="span1"></div>
			<div class="span3">
				<form:errors path="*" element="div" cssClass="alert alert-error" />
				<form:form modelAttribute="registerForm">
					<fieldset>
						<legend>New User</legend>
						<label>First Name</label>
						<div class="input-control text size2">
							<form:input type="text" value="" placeholder="" path="firstName" />
							<button class="btn-clear" tabindex=-1 type="button"></button>
						</div>
						<label>Last Name</label>
						<div class="input-control text size2">
							<form:input type="text" value="" placeholder="" path="lastName" />
							<button class="btn-clear" tabindex=-1 type="button"></button>
						</div>
						<label>Username</label>
						<div class="input-control text size3">
							<form:input type="text" value="" placeholder="" path="username" />
							<button class="btn-clear" tabindex=-1 type="button"></button>
						</div>
						<label>Password</label>
						<div class="input-control text siez3">
							<form:input type="password" placeholder="" path="password" />
							<button class="btn-clear" tabindex=-1 type="button"></button>
						</div>
						<input type="submit" value="Add" /> <input type="reset"
							value="Clear" />
					</fieldset>
					<input type="hidden" name="${_csrf.parameterName}"
						value="${_csrf.token}" />
				</form:form>
			</div>
		</div>
	</div>

另外更新base.jsp於導覽列加入register連結:

....

<body class="metro">
	                ........
			<span class="element-divider place-right"></span>
			<a class="element place-right" href="<spring:url value="/register"/>">Register</a>
			<span class="element-divider place-right"></span>
		</nav>
	</nav>
	<div>
		<sitemesh:write property='body'/>
	</div>

啟動Server,畫面如下:

點選Register連結,畫面如下

填入資料後.....發現無法新增使用者....但是login就可以登入,說明UserDetailsService是有效的,可能新增的部分需要debug一下

console log:

21:45:50 [http-nio-8080-exec-65] FilterChainProxy - /login at position 6 of 14 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
21:45:50 [http-nio-8080-exec-65] AntPathRequestMatcher - Checking match of request : '/login'; against '/login'
21:45:50 [http-nio-8080-exec-65] UsernamePasswordAuthenticationFilter - Request is to process authentication
21:45:50 [http-nio-8080-exec-65] ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
Hibernate: select dneuser0_.id as id1_2_, dneuser0_.enabled as enabled2_2_, dneuser0_.firstName as firstNam3_2_, dneuser0_.lastName as lastName4_2_, dneuser0_.password as password5_2_, dneuser0_.username as username6_2_ from DneUser dneuser0_ where dneuser0_.username=?
21:45:50 [http-nio-8080-exec-65] CompositeSessionAuthenticationStrategy - Delegating to org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy@1daf2665
21:45:50 [http-nio-8080-exec-65] CompositeSessionAuthenticationStrategy - Delegating to org.springframework.security.web.csrf.CsrfAuthenticationStrategy@7f435ca7
21:45:50 [http-nio-8080-exec-65] UsernamePasswordAuthenticationFilter - Authentication success. Updating SecurityContextHolder to contain: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@fec65191: Principal: org.springframework.security.core.userdetails.User@586034f: Username: admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 4D5444E275FFE3F96D726F1B05B43AAF; Granted Authorities: ROLE_ADMIN, ROLE_USER
21:45:50 [http-nio-8080-exec-65] SavedRequestAwareAuthenticationSuccessHandler - Using default Url: /
21:45:50 [http-nio-8080-exec-65] DefaultRedirectStrategy - Redirecting to '/SpringMVC/'

上一篇
[Security] 自訂驗證(Customize Authentication)使用UserDetailsService(I)
下一篇
[Interceptor]應用HandlerInterceptor於評估Controller的Performance
系列文
無痛學習SpringMVC與Spring Security31

尚未有邦友留言

立即登入留言