iT邦幫忙

2022 iThome 鐵人賽

DAY 12
1

今日目標,設置 web security。

Security

我們需要對網站做一些權限管理,包含登入等行為,就需要先設置 web security config,以下先做簡易登入設置。

  1. 先加入依賴
    <!-- web security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  2. 建立一個 package 名稱為 security
  3. 在 security 底下建立一個 java class,名稱為 WebSecurityConfig,填入內容:
    package com.example.security;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();
        }
    }
    
    • WebSecurityConfigurerAdapter:提供基本配置,雖然已經被棄用,但小弟才疏學淺,不管怎麼改寫都會報錯... 就只好先繼續用了...
    • @Configuration:聲明此類別是配置設定
    • @EnableWebSecurity:啟用 Security 所需的各項配置
    • passwordEncoder():自定義如何加密(雜湊)密碼,避免密碼以明文儲存
    • configure(HttpSecurity http):自定義安全性設置,例如:某些網頁需要更高權限才可瀏覽等
      • http.csrf().disable():取消 CSRF 設置,避免表單無法正常發送,以安全性考量而言,此設置不應該被禁用,但小弟無論如何都無法在頁面添加 CSRF Token 所以只好先禁用它了
  4. 再來,我們對網頁做權限管理,預期只讓登入的人看的到 hello world 的頁面,修改 WebSecurityConfig 的 configure,完整內容為:
    package com.example.security;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();
            http
                .authorizeRequests()
                    .antMatchers("/register").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .formLogin();
        }
    }
    
    • .authorizeRequests():做權限分配
    • .antMatchers("/register").permitAll():允許所有路徑是 /register 的請求,意思是所有人都可以瀏覽 register 的頁面
    • .anyRequest().authenticated():對所有請求要求驗證,意思是要登入才能瀏覽其他頁面
    • .and().formLogin():未通過驗證的請求都轉到 login 頁面,如果不設定這個,會直接在頁面上出現 403
  5. 這時候我們再去一次 hello world 的頁面,會發現頁面被強制跳轉到 login 頁面 (他已經幫忙寫好 login 頁面了,真貼心)
  6. 再來,我們先設定基本的登入權限,在 WebSecurityConfig 額外建立新的 configure,完整內容為:
    package com.example.security;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("admin")
                    .password(passwordEncoder().encode("123"))
                    .roles("ADMIN");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();
            http
                .authorizeRequests()
                    .antMatchers("/register").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .formLogin();
        }
    }
    
    • .inMemoryAuthentication():自定義合法的驗證
    • .withUser("admin"):使用者帳號設定為 admin
    • .password(passwordEncoder().encode("123")):密碼設定為 123,注意要先雜湊過,不然會無法登入
    • .roles("ADMIN"):設定角色來做權限管理,如果有多個角色也可以一起設定
  7. 這時候就去 login 頁面試試吧,應該能使用 admin 和 123 正常登入,並看到 hello world 的頁面~~

讀者們應該會有個疑問,該不會要手動打入特定帳號密碼才能登入吧,那註冊是寫辛酸的喔?/images/emoticon/emoticon09.gif
且慢,當然不是!小弟將在明天的文章實現自定義登入功能!

保護密碼

既然都設定了 password encoder,那就順便處理一下資料庫的密碼是明文這件事情吧~

  1. 修改 UserService 的 addUser,完整內容為:
    package com.example.user;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Service;
    import org.springframework.validation.annotation.Validated;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.ConstraintViolationException;
    import javax.validation.Validator;
    import java.util.Set;
    
    @Service
    @Validated
    public class UserService {
        @Autowired
        private UserRepository userRepository;
    
        @Autowired
        private Validator validator;
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        public UserModel findUserByEmail(String email) {
            return userRepository.findByEmail(email);
        }
    
        public UserModel findUserByUsername(String username) {
            return userRepository.findByUsername(username);
        }
    
        public Integer addUser(UserModel user) {
            Set<ConstraintViolation<UserModel>> violations = validator.validate(user);
            if (!violations.isEmpty()) {
                StringBuilder sb = new StringBuilder();
                for (ConstraintViolation<UserModel> constraintViolation : violations) {
                    sb.append(constraintViolation.getMessage());
                }
                throw new ConstraintViolationException(sb.toString(), violations);
            }
            user.setPassword(passwordEncoder.encode(user.getPassword()));
            UserModel newUser = userRepository.save(user);
            return newUser.getId();
        }
    }
    
  2. 再去註冊一次,會發現資料庫裡的密碼經過雜湊保護了!
    /images/emoticon/emoticon07.gif

上一篇
Day 10 - Entity Constraints
下一篇
Day 12 - 註冊完就要登入
系列文
Spring Boot... 深不可測31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
diu7me
iT邦新手 3 級 ‧ 2022-12-14 16:57:32

你好, 到了spring security 6.0 那兩個configure 已經變成這樣了.

@Bean
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("admin")
                .password(passwordEncoder().encode("123"))
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        /* anymatchers() deprecated and removed from spring security 6.0, use requestMatchers instead */
        http.cors().and().csrf().disable()
                .authorizeRequests().requestMatchers("/register").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin();

        // http....;

        return http.build();
    }

我要留言

立即登入留言