Cơ chế Spring Security với JWT

Hi, Mình đang tìm hiểu Spring Security với JWT có thắc mắc làm sao Spring có thể so sánh password nhập vào là đúng. Tức là luồng data nó sẽ gửi tới server như thế nào ? (đang vướng mắc vấn đề này mà chưa giải đáp được)
Nhiệm vụ của

authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));

là để làm gì vậy ?

	UserDetails userDetails = userDetailsService.loadUserByUsername(JsonUtil.convertToJson(user));
		UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
				userDetails,
				userDetails.getUsername(), 
				userDetails.getAuthorities());
		auth.setDetails(userDetails);
		SecurityContext securityContext = SecurityContextHolder.getContext();
		securityContext.setAuthentication(auth);
		
		authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));

SecurityConfig :

@Override
	@Bean
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}

	@Autowired
	public void globalUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
		System.out.println("globalUserDetails ...");
		auth.userDetailsService(userDetailsService);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		/*http.authorizeRequests()
				.antMatchers("/resources/**").permitAll()
				.antMatchers("/login**").permitAll()
				.antMatchers("/api/mobile/v1/login").permitAll()
				.anyRequest().authenticated()
				.and().formLogin().loginPage("/login").permitAll()
				.failureUrl("/login-error").usernameParameter("username").passwordParameter("password").and().logout()
				.logoutSuccessUrl("/login").permitAll().and().rememberMe()
				.key(applicationSettings.getRemmember_secret()).rememberMeServices(rememberMeServices()).and().csrf()
				.disable();*/
		
		
		http
        	.httpBasic().disable()
        	.csrf().disable()
        	.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
            .authorizeRequests()
            .antMatchers("/resources/**").permitAll()
			.antMatchers("/login**").permitAll()
			.antMatchers("/api/mobile/v1/login").permitAll()
			.anyRequest().authenticated()
			.and()
				.formLogin().loginPage("/login").permitAll()
				.failureUrl("/login-error").usernameParameter("username").passwordParameter("password")
			.and()
				.logout().logoutSuccessUrl("/login").permitAll()
			.and()
			.rememberMe()
			.key(applicationSettings.getRemmember_secret()).rememberMeServices(rememberMeServices())
            .and()
			.apply(new JwtSecurityConfigurer(jwtTokenProvider));
	}

Controlller :

@PostMapping(path = "/login")
	public String checkLogin(
			@RequestParam("branchCode") String branchCode,
			@RequestParam(value = "username") String username, 
			@RequestParam(value = "password") String password) {
		System.out.println("CheckLogin in this request !");
		Screenright sc = screenrightService.findByEmployeeAndBranch(username, password, branchCode);
		Map<String, String> result = new HashMap<String, String>();
		if (sc == null) {
			result.put("branchCode", "");
			result.put("username", "");
			result.put("password", "");
			result.put("error", "false");
			return JsonUtil.convertToJson(result);
		}
		
		List<String> user = new ArrayList<>();
		user.add(username);
		user.add(branchCode);
//		System.out.println("AuthenticationManager !");
		System.out.println("Load User By Username !");
		UserDetails userDetails = userDetailsService.loadUserByUsername(JsonUtil.convertToJson(user));
		UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
				userDetails,
				userDetails.getUsername(), 
				userDetails.getAuthorities());
		auth.setDetails(userDetails);
		SecurityContext securityContext = SecurityContextHolder.getContext();
		securityContext.setAuthentication(auth);
		
		authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
		result.put("branchCode", branchCode);
		result.put("username", username);
		System.out.println("Create token from login !");
		result.put("token", jwtTokenProvider.createToken(username, branchCode));
		return JsonUtil.convertToJson(result);
	}
  1. Tức là luồng data nó sẽ gửi tới server như thế nào?
    Trong spring security có 1 setting được gọi là FORM_LOGIN_FILTER, mặc định nó sẽ sử dụng class UsernamePasswordAuthenticationFilter để filter.
    Dữ liệu authentication sẽ được post lên 1 url (có thể custom url = setting entry-point-ref) , đầu tiên sẽ chạy qua filter này. Trong filter nó sẽ thực hiện call AuthenticationManager để authenticate thông tin user.
    Trong AuthenticationManager lại sử dụng AuthencationProvider (mặc định là DaoAuthenticationProvider) để thực hiện validate thông tin người dùng. DaoAuthenticationProvider sẽ call UserDetailService để xác thực thông tin người dùng.
    Bạn đọc source theo follow trên sẽ hiểu. Nếu muốn custom thì chỉ cần extend lại các class trên là được.

  2. Để kết hợp được với JWT, thì có 2 bước :

    1. Tạo JWT sau khi login success: Có thể tạo trong 1 custom AuthenticationFilter (override successfulAuthentication) hoặc bất cứ chỗ nào bạn muốn ( thường là ở trong Filter)
    2. Tạo 1 Filter để verify token : Thường sẽ sử dụng BasicAuthenticationFilter để verify JWT.
4 Likes

Đầu tiên em nhập username vs password call Controller ở LoginController.class này để login

@PostMapping(path = "/login")
     public String checkLogin(@RequestParam("branchCode") String branchCode,
			@RequestParam(value = "username") String username, 
			@RequestParam(value = "password") String password)

Được permit all ở SecurityConfig.class -) .antMatchers("/api/mobile/v1/login").permitAll()

Sau đó trong LoginController.class gọi (Em nghĩ em có vấn đề ở đây)

UserDetails userDetails = userDetailsService.loadUserByUsername(JsonUtil.convertToJson(user));
		UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
				userDetails,
				userDetails.getUsername(), 
				userDetails.getAuthorities());
		auth.setDetails(userDetails);
		SecurityContext securityContext = SecurityContextHolder.getContext();
		securityContext.setAuthentication(auth);
		
		authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));

Đưa data vừa nhập vào authenticationManager
Tiếp theo lấy data vừa nhận được so sánh với nhau qua config ở SecurityConfig.class

public DaoAuthenticationProvider getDaoAuthenticationProvider() {
		DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
		daoAuthenticationProvider.setUserDetailsService(userDetailsService);
		return daoAuthenticationProvider;
	}

	@Autowired
	public void globalUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
//		System.out.println("globalUserDetails ...");
//		auth.userDetailsService(userDetailsService);
		auth.authenticationProvider(getDaoAuthenticationProvider());
	}

Thành công thì tạo token

result.put("token", jwtTokenProvider.createToken(username, branchCode));
		return JsonUtil.convertToJson(result);

Sau mỗi lần request tới api được authen thì server sẽ filter tới

.apply(new JwtSecurityConfigurer(jwtTokenProvider));
public class JwtSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private JwtTokenProvider jwtTokenProvider;

    public JwtSecurityConfigurer(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
    	System.out.println("JwtSecurityConfigurer ...");
        JwtTokenAuthenticationFilter customFilter = new JwtTokenAuthenticationFilter(jwtTokenProvider);
        http.exceptionHandling()
        .authenticationEntryPoint(new JwtAuthenticationEntryPoint())
        .and()
        .addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

Và kiểm tra token đó

public class JwtTokenAuthenticationFilter extends GenericFilterBean {
	

    private JwtTokenProvider jwtTokenProvider;

    public JwtTokenAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
        throws IOException, ServletException {
    	System.out.println("JwtTokenAuthenticationFilter ...");
    	
        String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
        if (token != null && jwtTokenProvider.validateToken(token)) {
            Authentication auth = jwtTokenProvider.getAuthentication(token);

            if (auth != null) {
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }
        filterChain.doFilter(req, res);
    }

}

Không biết như vậy có đúng không ạ ?
Do mới tìm hiểu nên không biết code nó hoạt động ra sao. Cứ thắc mắc khi nào cái nào được gọi , trước sau nó như nào :(((

Chưa tìm thấy hướng giải quyết ạ :frowning:

JwtAuthenticationFilter Có nhiệm vụ kiểm tra request của người dùng trước khi nó tới đích. Nó sẽ lấy Header Authorization ` ra và kiểm tra xem chuỗi JWT người dùng gửi lên có hợp lệ không.
Thông tin nó mã hóa gửi kèm vào Header, và thường thì ngta k lưu thông tin quan trọng như password vào Token :smiley: Tui cũng mới tìm hiểu k biết có đúng k nữa :smiley:

3 Likes

Login thành công -> tạo token. Vậy spring nó phải so sánh username và password trước khi thực hiện JWT

Trả lời cho câu hỏi của bạn " Làm sao spring có thể so sánh username và password với database?"

Spring nó so sánh password với class UserDetails của nó.

Nhiệm vụ của bạn là chuyển thàng User của bạn thành UserDetails :smiley:

Thì đây, tèn ten!! :smiley: So sánh username có trong database và trả ra một class X gì đó.

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {
       // Logic here
        return new MyUserPrincipal(user);
    }
}


Class X nó lại implement UserDetails , và nó import cái User của bạn vào :smiley:  :v 
public class MyUserPrincipal **implements UserDetails** {
    private User user;
    //Override here
}

Trả lời muộn chắc giờ bạn đã k cần nữa để cho người sau vậy :))))))))))))))))))

1 Like
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?