天天看點

Spring Boot 整合 Spring Security(配置登入/登出)

Spring Boot 整合 Spring Security ,配置登入/登出,如:登入接口,登入成功或失敗後的響應等。

1 建立工程

建立 Spring Boot 項目

spring-boot-springsecurity-login

,添加

Web/Spring Security

依賴,如下:

Spring Boot 整合 Spring Security(配置登入/登出)

最終的依賴如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
           

2 配置 Spring Security

新增

SecurityConfig

配置類,如下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder() {
//        return NoOpPasswordEncoder.getInstance();// 密碼不加密
        return new BCryptPasswordEncoder();// 密碼加密
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 在記憶體中配置 2 個使用者
        /*auth.inMemoryAuthentication()
                .withUser("admin").password("123456").roles("admin")
                .and()
                .withUser("user").password("123456").roles("user");// 密碼不加密*/

        auth.inMemoryAuthentication()
                .withUser("admin").password("$2a$10$fB2UU8iJmXsjpdk6T6hGMup8uNcJnOGwo2.QGR.e3qjIsdPYaS4LO").roles("admin")
                .and()
                .withUser("user").password("$2a$10$3TQ2HO/Xz1bVHw5nlfYTBON2TDJsQ0FMDwAS81uh7D.i9ax5DR46q").roles("user");// 密碼加密
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 開啟登入配置
        http.authorizeRequests()
                // 表示 admin 角色能通路
                .antMatchers("/admin/**").hasRole("admin")
                // 表示 admin 或 user 角色都能通路
                // .antMatchers("/user/**").hasAnyRole("admin", "user")
                // 表示 admin 或 user 角色都能通路
                .antMatchers("/user/**").access("hasAnyRole('admin','user')")
                // 表示剩餘的其他接口,登入之後就能通路
                .anyRequest().authenticated()
                .and()
                .formLogin()
                // 表示登入頁的位址,例如當你通路一個需要登入後才能通路的資源時,系統就會自動給你通過【重定向】跳轉到這個頁面上來
                .loginPage("/login")
                // 表示處理登入請求的接口位址,預設為 /login
                .loginProcessingUrl("/doLogin")
                // 定義登入時,使用者名的 key,預設為 username
                .usernameParameter("uname")
                // 定義登入時,密碼的 key,預設為 password
                .passwordParameter("passwd")
                // 登入成功的處理器
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        Map<String, Object> map = new HashMap<>();
                        map.put("status", 200);
                        map.put("msg", authentication.getPrincipal());
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                // 登入失敗的處理器
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        Map<String, Object> map = new HashMap<>();
                        map.put("status", 401);
                        if (e instanceof LockedException) {
                            map.put("msg", "賬戶被鎖定,登入失敗!");
                        } else if (e instanceof BadCredentialsException) {
                            map.put("msg", "使用者名或密碼輸入錯誤,登入失敗!");
                        } else if (e instanceof DisabledException) {
                            map.put("msg", "賬戶被禁用,登入失敗!");
                        } else if (e instanceof AccountExpiredException) {
                            map.put("msg", "賬戶過期,登入失敗!");
                        } else if (e instanceof CredentialsExpiredException) {
                            map.put("msg", "密碼過期,登入失敗!");
                        } else {
                            map.put("msg", "登入失敗!");
                        }
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                // 和表單登入相關的接口統統都直接通過
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                // 登出成功的處理器
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        Map<String, Object> map = new HashMap<>();
                        map.put("status", 200);
                        map.put("msg", "登出登入成功!");
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                .and()
                .csrf().disable()
                .exceptionHandling()
                // 無通路權限的處理器
                .accessDeniedHandler(new AccessDeniedHandler() {
                    @Override
                    public void handle(HttpServletRequest req, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        Map<String, Object> map = new HashMap<>();
                        map.put("status", 403);
                        map.put("msg", "無通路權限!");
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                // 預設情況下使用者直接通路一個需要認證之後才可以通路的請求時,會被重定向到.loginPage("/login"),前後端分離時會導緻跨域。
                // 增加如下配置後,就不會發生重定向操作了,服務端會直接給浏覽器一個 JSON 提示
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException authException) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        Map<String, Object> map = new HashMap<>();
                        map.put("status", 401);
                        if (authException instanceof InsufficientAuthenticationException) {
                            map.put("msg", "通路失敗,請先登入!");
                        } else {
                            map.put("msg", "通路失敗!");
                        }
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                });
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 配置不需要攔截的請求位址,即該位址不走 Spring Security 過濾器鍊
        web.ignoring().antMatchers("/vercode");
    }
}
           

3 測試

新增

HelloController

測試類,如下:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

    @GetMapping("/admin/hello")
    public String admin() {
        return "hello admin";
    }

    @GetMapping("/user/hello")
    public String user() {
        return "hello user";
    }

    @GetMapping("/login")
    public String login() {
        return "please login";
    }
}
           

項目啟動之後,用

Postman

完成測試,如下:

通路

/hello

接口,提示先登入。

Spring Boot 整合 Spring Security(配置登入/登出)

通路

/doLogin

接口登入失敗,因為

key

不對。

Spring Boot 整合 Spring Security(配置登入/登出)

用自定義的

key

通路

/doLogin

接口登入成功。

Spring Boot 整合 Spring Security(配置登入/登出)

再通路

/hello

接口,傳回正常。

Spring Boot 整合 Spring Security(配置登入/登出)
  • Spring Boot 教程合集(微信左下方閱讀全文可直達)。
  • Spring Boot 教程合集示例代碼:https://github.com/cxy35/spring-boot-samples
  • 本文示例代碼:https://github.com/cxy35/spring-boot-samples/tree/master/spring-boot-security/spring-boot-springsecurity-login

掃碼關注微信公衆号 程式員35 ,擷取最新技術幹貨,暢聊 #程式員的35,35的程式員# 。獨立站點:https://cxy35.com

Spring Boot 整合 Spring Security(配置登入/登出)

繼續閱讀