Security详解—自定义认证AuthenticationProvider(4)

        对于Security默认使用表单认证,而表单认证则使用的是UsernamePasswordAuthenticationFilter过滤器。这篇文章主要是用于自定义认证。

AuthenticationManager默认使用ProviderManager,而ProviderManager的AuthenticationProvider默认DaoAuthenticationProvider,这篇文章就是重写DaoAuthenticationProvider。

1.Authentication类MyUsernamePasswordAuthenticationToken

        新建一个Authentication类,用于在认证过程中存放认证信息。

public class MyUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {

    public MyUsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(principal, credentials, authorities);
    }

}

2.新建过滤器MyFilter

        新建的过滤器继承AbstractAuthenticationProcessingFilter类

public class MyFilter extends AbstractAuthenticationProcessingFilter {

    public MyFilter() {
        super(new AntPathRequestMatcher("/mylogin", "GET"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (!request.getMethod().equals("GET")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = request.getParameter("username");
            username = username != null ? username.trim() : "";
            String password = request.getParameter("password");
            password = password != null ? password : "";
            MyUsernamePasswordAuthenticationToken authRequest = new MyUsernamePasswordAuthenticationToken(username, password, Collections.emptyList());
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }
}

3. UserDetailsService接口实现类的实现 

        主要用户查询用户信息 

@Slf4j
public class MyUserDetailsService implements UserDetailsService {

    Map<String, String> user = new HashMap();
    Map<String, List<String>> userRole = new HashMap<>();

    {
        user.put("ls", "123456");
        user.put("zs", "111111");

        //hasRole 的处理逻辑和 hasAuthority 似乎是一样的,只是hasRole 这
        // 里会自动给传入的字符串前缀(默认是ROLE_ ),
        // 使用 hasAuthority 更具有一致性,不用考虑要不要加 ROLE_ 前缀,
        // 在UserDetailsService类的loadUserByUsername中查询权限,也不需要手动增加。
        // 在SecurityExpressionRoot 类中hasAuthority 和 hasRole
        // 最终都是调用了 hasAnyAuthorityName 方法。
        userRole.put("ls", Arrays.asList("ROLE_admin"));

        userRole.put("zs", Arrays.asList("query"));
    }

    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("MyUserDetailsService.loadUserByUsername 开始");
        if (!user.containsKey(username)) {
            throw new UsernameNotFoundException("username is not exists");
        }
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        if (userRole.containsKey(username)) {
            authorities = userRole.get(username).stream().map(row -> new SimpleGrantedAuthority(row))
                    .collect(Collectors.toList());
        }
        return new User(username, passwordEncoder.encode(user.get(username)), authorities);
    }
}

4. 认证核心代码类MyAuthenticationProvider

        MyAuthenticationProvider类主要用于用户的登录认证

@Slf4j
public class MyAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    UserDetailsService myUserDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //获取输入用户名
        String username = authentication.getName();

        //获取输入明文密码
        String password = (String) authentication.getCredentials();

        //根据用户名查询匹配用户
        User user = (User) myUserDetailsService.loadUserByUsername(username);
        //判断用户账户状态
        if (!user.isEnabled()) {
            throw new DisabledException("该账户已被禁用,请联系管理员");

        } else if (!user.isAccountNonLocked()) {
            throw new LockedException("该账号已被锁定");

        } else if (!user.isAccountNonExpired()) {
            throw new AccountExpiredException("该账号已过期,请联系管理员");

        } else if (!user.isCredentialsNonExpired()) {
            throw new CredentialsExpiredException("该账户的登录凭证已过期,请重新登录");
        }
        //验证密码
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new BadCredentialsException("输入密码错误!");
        }
        return new MyUsernamePasswordAuthenticationToken(username, password, user.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        log.info("supports class:" + authentication.getName());
//        return authentication.equals(MyUsernamePasswordAuthenticationToken.class);
        return true;
    }
}

5. SpringSecurity核心配置

     继承自WebSecurityConfigurerAdapter,作用为开启自定义配置,登录成功处理器、失败处理器等,定义规则什么资源拦截,什么可以直接访问等,即上面进行的操作,都要在这个类配置开启才能生效。     

  •  方式1: 登录方式可以直接使用/mylogin.html界面登录,它可以触发UsernamePasswordAuthenticationFilter过滤器进行认证。
  •  方式2: 使用GET方式调用mylogin接口,触发MyFilter进行认证。
  • 退出登录使用logout接口
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    MyAuthenticationProvider myAuthenticationProvider;

    /**
     * formLogin() 默认使用了UsernamePasswordAuthenticationFilter
     * 默认使用了DaoAuthenticationProvider认证逻辑,使用configure可以配置UserDetailsService
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //忽略hellopost接口
        http.csrf().disable();

        http.authorizeRequests()
                .antMatchers("/index", "/loginalert", "/mylogin").permitAll()
                .antMatchers("/helloadmin").hasRole("admin")
                .antMatchers("/hellouser").hasAuthority("query")
                .anyRequest().authenticated()
                // 方式1: 默认使用页面登录
                .and().formLogin().loginPage("/mylogin.html")
                .usernameParameter("uname").passwordParameter("passwd")
                .permitAll()
                .loginProcessingUrl("/doLogin")
                .failureHandler((request, response, exception) -> {
                    String message = "login fail:" + exception.getMessage();
                    response.setContentType("text/html;charset=utf-8");
                    response.getWriter().write("<script>alert('" + message + "');window.location.href='/mylogin.html'</script>");
                }).successHandler(((request, response, authentication) -> {
                    String message = "login success:" + authentication.getName();
                    response.setContentType("text/html;charset=utf-8");
                    response.getWriter().write("<script>alert('" + message + "');window.location.href='/index'</script>");
                }));

        //权限失败
        http.exceptionHandling().accessDeniedHandler(((request, response, accessDeniedException) -> {
            String message = "access fail:" + accessDeniedException.getMessage();
            response.setContentType("text/html");
            response.getWriter().write("<script>alert('" + message + "');window.location.href='/index'</script>");
        }));


        http.authenticationManager(authenticationManager());
        http.addFilterBefore(myFilter(), UsernamePasswordAuthenticationFilter.class);
    }

//    //MyAuthenticationProvider中使用了UserDetailsService,所以不需要设置
//    @Autowired
//    UserDetailsService myUserDetailsService;
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        //auth.userDetailsService(myUserDetailsService);
//    }

    /**
     * 设置UsernamePasswordAuthenticationFilter的setAuthenticationManager
     *
     * @return
     * @throws Exception
     */
    public AuthenticationManager authenticationManager() throws Exception {
        return new ProviderManager(myAuthenticationProvider);
    }

    /**
     * 方式2: 自定义过滤器登录
     *
     * @return
     * @throws Exception
     */
    @Bean
    public MyCustomizeLoginFilter myFilter() throws Exception {
        MyCustomizeLoginFilter filter = new MyCustomizeLoginFilter();
        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationSuccessHandler(((request, response, authentication) -> {
            String message = "myfilter login success:" + authentication.getName();
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().write("<script>alert('" + message + "');window.location.href='/index'</script>");
        }));

        filter.setAuthenticationFailureHandler((request, response, exception) -> {
            String message = "myfilter login fail:" + exception.getMessage();
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().write("<script>alert('" + message + "');window.location.href='/mylogin.html'</script>");
        });
        return filter;
    }

}

 配置类:

@Configuration
public class MyConfig {


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

    @Bean
    UserDetailsService myUserDetailsService() {
        return new MyUserDetailsService();
    }

    @Bean
    MyAuthenticationProvider myAuthenticationProvider() {
        return new MyAuthenticationProvider();
    }

}

6.测试验证

  •  方式1: mylogin.html登录触发UsernamePasswordAuthenticationFilter过滤器

74cbddd7df52444bbeda3cf06b42aa0b.png

  • 方式2: mylogin接口,触发MyFilter过滤器

557e9f873fc9456391c6bdc265a24e12.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值