SpringSecurity自定義註解和處理器

  • 2021 年 12 月 20 日
  • 筆記

登錄功能

添加一個配置類

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }

    @Bean
    PasswordEncoder password() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.formLogin() // 表單登錄
                .and().authorizeRequests()
                .anyRequest()//其他請求
                .authenticated();//需要認證

        //關閉csrf
        http.csrf().disable();
    }
}

登錄的實現類

/**
 * SpringSecurity 自動調用該類
 * 登錄實現類 默認 繼承 UserDetailsService
 */
@Service("userDetailsService") 
public class UserDetailsServiceImpl implements UserDetailsService {
    @Resource
    private AdminMapper adminMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<Admin> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        Admin admin = adminMapper.selectOne(wrapper);
        if (admin == null) {
            throw new UsernameNotFoundException("用戶名不存在");
        }
        List<GrantedAuthority> auths = new ArrayList<>();
        return new User(admin.getUsername(), new BCryptPasswordEncoder().encode(admin.getPassword()), auths);
    }
}

自定義登錄頁面

只需要修改一下配置類

	@Override
    protected void configure(HttpSecurity http) throws Exception {

        http.formLogin() // 表單登錄
                .loginPage("/login") //配置登錄頁面  引入了thymeleaf
                .loginProcessingUrl("/user/login")//設置哪個是登錄的url
                .permitAll()
                .and().authorizeRequests()
                .anyRequest()//其他請求
                .authenticated();//需要認證

        //關閉csrf
        http.csrf().disable();
    }

自定義認證成功或失敗狀態碼

如果你想要在認證成功或者失敗後拿到你自己定義的狀態碼,那你可以參考以下步驟

主要是下面的兩個處理器

/**
 * 用戶認證失敗處理類
 */
@Component("userLoginAuthenticationFailureHandler")
public class UserLoginAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        System.out.println("===" + exception.getMessage());
        JsonData jsonData = null;
        jsonData = new JsonData(403,"用戶名或密碼錯誤");

        String json = new Gson().toJson(jsonData);//包裝成Json 發送的前台
        System.out.println(json);
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();

        out.write(json);
        out.flush();
        out.close();
    }
}


/**
 * 用戶認證成功處理類
 */
@Component("userLoginAuthenticationSuccessHandler")
public class UserLoginAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        JsonData jsonData = new JsonData(200,"認證OK");
        String json = new Gson().toJson(jsonData);
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();

        out.write(json);
        out.flush();
        out.close();
    }
}

在配置類中添加自己寫的兩個處理類

注入自己寫的兩個處理器

    @Resource
    private UserLoginAuthenticationFailureHandler userLoginAuthenticationFailureHandler;//驗證失敗的處理類

    @Resource
    private UserLoginAuthenticationSuccessHandler userLoginAuthenticationSuccessHandler;//驗證成功的處理類

配置上兩個處理類

@Override
    protected void configure(HttpSecurity http) throws Exception {

        http.formLogin() // 表單登錄
                .loginPage("/login") //配置登錄頁面  引入了thymeleaf
                .loginProcessingUrl("/user/login")//設置哪個是登錄的url
                .failureHandler(userLoginAuthenticationFailureHandler)//驗證失敗處理
                .successHandler(userLoginAuthenticationSuccessHandler)//驗證成功處理
                .permitAll()
                .and().authorizeRequests()
                .anyRequest()//其他請求
                .authenticated();//需要認證

        //關閉csrf
        http.csrf().disable();
    }

此時的登錄頁面

<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登錄</title>

</head>
<body>
<section>
    <span>用戶名:<input type="text" id="username" name="username"/> <br/> </span>
    <span>用戶密碼:<input type="password" id="password" name="password"/> <br/> </span>
    <button onclick="login()">登錄</button>
</section>

<script type="text/javascript" src="../static/js/jquery.js" th:src="@{/js/jquery.js}"></script>
<script>
    function login(){
        let username = document.getElementById("username");
        let password = document.getElementById("password");

        let username_and_password = {
            username:username.value,
            password:password.value
        }
        $.ajax({
            type:"Post",
            url:"/user/login",
            data:username_and_password,
            success:function (data) {
                console.log(data)
                console.log(data.code)
                console.log(data.msg)
                if (data.code == 200){ //拿到自己定義的狀態碼進行跳轉
                    alert(data.msg)
                    // window.location.href = "/hello";
                    // console.log("hehe")
                }else if (data.code == 403){
                    alert(data.msg)
                } else {
                    alert("客戶端出錯")
                }
            }
        })
    }

</script>
</body>
</html>

寫一個註解

標註該註解的方法或類,直接放行。

/**
 * 聲明不用攔截的介面
 **/
@Target({ElementType.TYPE, ElementType.METHOD}) //該註解可以用在類上或者方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface NoAuthentication {
}

配置類中主要是以下這個方法

//設置哪些不需要認證
    @Override
    public void configure(WebSecurity web) throws Exception {
        //靜態資源放行,我就隨便寫寫,根據自己靜態資源結構去寫。
        String[] urls = new String[]{
                "/js/**",
                "/imgs/**",
                "/css/**"
        };
        ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext();
        List<String> whiteList = new ArrayList<>();
        for (String url : urls) {
            whiteList.add(url);
        }

        RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        // 獲取url與類和方法的對應資訊
        Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> requestMappingInfoHandlerMethodEntry : map.entrySet()) {
            RequestMappingInfo key = requestMappingInfoHandlerMethodEntry.getKey();
            HandlerMethod value = requestMappingInfoHandlerMethodEntry.getValue();
            Set<String> patterns = key.getPatternsCondition().getPatterns();
            //無需許可權都可以訪問的類型
            NoAuthentication noAuthentication = value.getBeanType().getAnnotation(NoAuthentication.class);
            if (null != noAuthentication) {//整個controller不需要許可權訪問的
                RequestMapping annotation = value.getBeanType().getAnnotation(RequestMapping.class);
                if (null != annotation) {
                    String path = annotation.value()[0];
                    String suffix = "**";
                    if (path.lastIndexOf("/") != path.length() - 1)
                        suffix = "/**";
                    String s = path + suffix;
                    if (!whiteList.contains(s)) {
                        whiteList.add(s);
                    }
                }
            } else {//方法不需要許可權訪問的
                NoAuthentication annotation = value.getMethod().getAnnotation(NoAuthentication.class);
                if (null != annotation) {
                    patterns.forEach(p -> {
                        if (!whiteList.contains(p)) {
                            whiteList.add(p);
                        }
                    });
                }
            }
        }
        System.out.println("-----");
        for (String s : whiteList) {
            System.out.println(s);
        }
        urls = whiteList.toArray(urls);
        super.configure(web);
        web.httpFirewall(defaultHttpFirewall());

        web.ignoring().antMatchers(urls);
    }
/**
     * 允許出現雙斜杠的URL
     *
     * @return
     */
    @Bean
    public HttpFirewall defaultHttpFirewall() {
        return new DefaultHttpFirewall();
    }

測試:把註解放在某個方法或者某個類上面,可以發現不用登陸也能直接進行介面的訪問

作用:如果你有一些介面是不需要認證的,比如說你去淘寶逛東西,你只是看看的話,要是讓你登陸的話就有些不合理了,這時你就可以在類似需求的類上加上該註解,就能實現不用登陸也能訪問。

歡迎

源碼://github.com/zhi-ac/security_demo
如果覺得有收穫,不妨花個幾秒鐘點個贊,歡迎關注我的公眾號玩編程地碼農,目前在寫數據結構與演算法、電腦基礎、java相關的知識。