首页 > 技术文章 > SpringSecurity自定义注解和处理器

wbcdmn 2021-12-20 20:11 原文

登录功能

添加一个配置类

@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="http://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();
    }

测试:把注解放在某个方法或者某个类上面,可以发现不用登陆也能直接进行接口的访问

作用:如果你有一些接口是不需要认证的,比如说你去淘宝逛东西,你只是看看的话,要是让你登陆的话就有些不合理了,这时你就可以在类似需求的类上加上该注解,就能实现不用登陆也能访问。

欢迎

源码:https://github.com/zhi-ac/security_demo
如果觉得有收获,不妨花个几秒钟点个赞,欢迎关注我的公众号玩编程地码农,目前在写数据结构与算法、计算机基础、java相关的知识。

推荐阅读