Bảo mật với spring

I. Giới thiệu

Hai vấn đề trong bảo mật thông tin: Authentication(Bạn là ai?) và authorization(bạn có quyền truy cập?). Một số người người gọi là "access control" thay cho "authorization".

II. Authentication:

  • Strategy chính trong phần này là AuthenticationManager:
    public interface AuthenticationManager {
        Authentication authenticate(Authentication authentication) throws AuthenticationException;
    }

có 3 khả năng xảy ra với hàm authenticate():

  1. Trả lại 1 object Authentication, nếu authentication có thể xác thực đầu vào là principal hợp lệ.
  2. Throw exeption AuthenticationExeption nếu đầu vào là principal không hợp lệ.
  3. Trả lại giá trị null nếu nó không thể xử lý.

AuthenticationExeption là một runtime exeption. Nó thường được vận dụng bằng 1 cách chung cho ứng dụng, tùy theo style hoặc mục đích của ứng dụng. Ví dụ, một web UI sẽ render một trang nói rằng việc xác thực lỗi, và sẽ trả lại một 401 response trong header.

  • Trong spring, class ProviderManager được khai báo để xác thực người dùng, cũng như gán quyền truy cập cho người dùng. Như trong ví dụ sau, tôi xác thực người dùng từ database.
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        ... // web stuff here
        @Autowired
        public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
            auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery("select username,password, enabled from users where username=?")
                    .authoritiesByUsernameQuery("select username, role from user_roles where username=?");
        }
    }

Ví dụ này, bạn truy cập trực tiếp vào database để lấy thông tin người dùng trực tiếp trên database thông qua các bảng users và user_roles. Lưu ý, ở trường role của bảng user_roles, nội dung phải có dạng ROLE_<role_name>, ví dụ: ROLE_USER. Tuy nhiên, tôi không muốn tổ chức như quy ước có ẵn của nó, bộ dữ liệu của tôi khác quy chuẩn của spring. Vậy tôi cần phải làm gì?

  • Các bạn có thể sử dụng cách sau đây:
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        ... // web stuff here
        @Bean
        public DaoAuthenticationProvider authProvider() {
            DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
            UserDetailsService userDetails = new UserInfo();
            authProvider.setUserDetailsService(userDetails);
            return authProvider;
        }
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(authProvider());
        }
    }
  • Các bạn sử dụng phương thức configureGlobal, bên cạnh đó, khai báo thêm class UserInfo kế thừa từ interface UserDetailsService như sau:
    public class UserInfo implements UserDetailsService {
        @Autowired
        private UserRepository userRepository;

        @Override
        public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
            // TODO Auto-generated method stub
            GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
            com.vbee.sso.entities.User result = userRepository.findByEmail(email);
            if (result == null) {
                throw new UsernameNotFoundException(email + " does not exist");
            }
            UserDetails userDetails = (UserDetails) new User(result.getEmail(),
                    result.getPassword() + "|" + result.getFacebookId() + "|" + result.getGoogleId(),
                    Arrays.asList(authority));
            return userDetails;
        }
    }
  • Lưu ý rằng, sau khi client gửi các thông tin, nó sẽ sử dụng luôn thông tin đó để check xác thực, vì 1 vài lý do nào đó, tôi cần xử lý thông tin thô từ client đẩy lên trước khi kiểm tra với dữ liệu của UserInfo. Vậy tôi cần phải làm như thế nào? Tôi thêm 1 lớp là MyAuthentication kế thừa lại lớp DaoAuthenticationProvider. Bên cạnh đó, tôi thực hiện ghi đè phương thức authenticate. Trong hàm authProvider() ở class WebSecurityConfig đã khai báo ở trên, thay vì trả lại DaoAuthenticationProvider, tôi return lại MyAuthentication. Và lớp MyAuthentication được tôi thay đổi như sau:
    public class MyAuthentication extends DaoAuthenticationProvider {
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            //Check validate
            GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
            UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(authentication.getName(),	Encode.MD5(authentication.getCredentials()), Arrays.asList(authority));
            result.setDetails(authentication.getDetails());
            return result;
        }
    }
  • Chúng ta cần thực hiện kiểm tra các điều kiện, nếu thỏa mãn, bạn chỉ cần khai báo 1 user class kế thừa từ lớp AbstractAuthenticationToken. Trong ví dụ, tôi sử dụng luôn lớp UsernamePasswordAuthenticationToken. *Lưu ý: không nên sử dụng hàm setAuthenticated trực tiếp của lớp UsernamePasswordAuthenticationToken để set giá trị true, vì điều đó là không được phép. Bạn cần phải tạo 1 insteand mới, giá trị mặc định của authenticated khi tạo mới là true.

Trong kì tiếp theo, tôi sẽ trình bày xây dựng hệ thống xác thực Single sign on bằng spring! Cảm ơn các bạn! *Smt