Spring Security несколько способов входа в систему

Spring Boot

предисловие

Наиболее часто используемым методом в Интернете является схема UserDetailsService, в которой сложно реализовать несколько методов входа в систему. В этой статье используется схема Filter+Manager+Provider+Token для реализации нескольких методов входа в систему. Кодов слишком много, в этой статье показан только один из способов входа. Кратко опишите, что делают эти вещи:

  • Фильтр отвечает за перехватывающие запросы и менеджер звонков
  • Менеджер отвечает за управление несколькими провайдерами и выбор соответствующего провайдера для аутентификации.
  • Провайдер отвечает за аутентификацию, проверку паролей учетных записей и т. д.
  • Токен — это информация для аутентификации, включая пароль учетной записи, который можно определить самостоятельно.

Код фильтра

Сначала посмотрите на код фильтра

public class UserPasswordAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
    protected UserPasswordAuthenticationProcessingFilter() {
        super("/login");//认证url
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
        //根据前端参数判断是哪种登录类型,封装成对应方式的Token,提交给Manager
        if("password".equals(request.getParameter("type"))){
            String user = request.getParameter("username");
            String password = request.getParameter("password");
            System.out.println(user);
            //把账号密码封装成token,传给manager认证
            return getAuthenticationManager().authenticate(new UserPasswordAuthenticationToken(user,password));
        }else{
            //其他登录方式
            return null;
        }
    }
}

Код токена

Суть токена заключается в двух методах построения: один используется до аутентификации, а другой — после аутентификации.

public class UserPasswordAuthenticationToken extends AbstractAuthenticationToken {
    //自定义属性
    private long id;
    private long storeId;
    private String isAdmin;
    private String user;
    private String password;

    //一些get、set方法
    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    
    public long getId(){
        return this.id;
    }
    
    public void setId(long id) {
        this.id = id;
    }

    public long getStoreId() {
        return storeId;
    }

    public void setStoreId(long storeId) {
        this.storeId = storeId;
    }
    /**
     * 认证时过滤器通过这个方法创建Token,传入前端的参数
     * @param user
     * @param password
     */
    public UserPasswordAuthenticationToken(String user,String password){
        super(null);
        this.user = user;
        this.password=password;
        //关键:标记未认证
        super.setAuthenticated(false);
    }

    /**
     * 认证通过后Provider通过这个方法创建Token,传入自定义信息以及授权信息
     * @param id
     * @param storeId
     * @param isAdmin
     * @param authorities
     */
    public UserPasswordAuthenticationToken(long id,Long storeId,String isAdmin,Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.id = id;
        if(storeId==null){
            this.storeId=0;
        }else{
            this.storeId=storeId;
        }
        this.isAdmin=isAdmin;
        //关键:标记已认证
        super.setAuthenticated(true);
    }
    
    //父类获取授权信息的两个方法,区别是啥不太清楚,但都可以返回自定义信息
    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return this.isAdmin;
    }

}

Код провайдера

public class UserPasswordAuthentiactionProvider implements AuthenticationProvider {

    @Autowired
    private UserDao userDao;

    /**
     * 在此方法进行认证
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //认证代码,认证通过返回认证对象,失败返回null
        UserPasswordAuthenticationToken userPasswordAuthenticationToken = (UserPasswordAuthenticationToken)authentication;
        if(userPasswordAuthenticationToken.getUser()==null || userPasswordAuthenticationToken.getPassword()==null){
            return null;
        }
        User user=userDao.login(userPasswordAuthenticationToken);
        if(user!=null){
            //授予用户权限
            String role;
            if(user.getIsAdmin().equals("false")){
                role="ROLE_USER";//此处的权限是固定格式,否则不能用:ROLE_+权限,大写
            }else{
                role="ROLE_ADMIN";//管理员权限
            }
            //返回认证后的Token
            return new UserPasswordAuthenticationToken(user.getId(),user.getStoreId(),user.getIsAdmin(),
                    Arrays.asList(new SimpleGrantedAuthority(role)));
        }
        return null;
    }

    /**
     * 此方法决定Provider能够处理哪些Token,此Provider只能处理密码登录方式的Token,这里也是多种登录方式的核心
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(Class<?> aClass) {
        //Manager传递token给provider,调用本方法判断该provider是否支持该token。不支持则尝试下一个filter
        //本类支持的token类:UserPasswordAuthenticationToken
        return (UserPasswordAuthenticationToken.class.isAssignableFrom(aClass));
    }
}

Конфигурация безопасности

В основном это зависит от конфигурации ядра, которая отмечена в комментариях. Другие конечные точки входа, обработка сбоев и т. д. могут быть изменены в соответствии с вашими потребностями, и нет необходимости в том, чтобы они были точно такими же.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //核心:provider配置
    @Bean
    UserPasswordAuthentiactionProvider userPasswordAuthentiactionProvider(){
        return new UserPasswordAuthentiactionProvider();
    }
    //核心:filter配置
    UserPasswordAuthenticationProcessingFilter userPasswordAuthenticationProcessingFilter(AuthenticationManager authenticationManager){
        UserPasswordAuthenticationProcessingFilter userPasswordAuthenticationProcessingFilter = new UserPasswordAuthenticationProcessingFilter();
        //为filter设置管理器
        userPasswordAuthenticationProcessingFilter.setAuthenticationManager(authenticationManager);
        //登录成功后跳转
        userPasswordAuthenticationProcessingFilter.setAuthenticationSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> {
            httpServletResponse.sendRedirect("/");
        });
        userPasswordAuthenticationProcessingFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/getLogin?error"));
        return userPasswordAuthenticationProcessingFilter;
    }
    //配置登录端点
    @Bean
    LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint(){
        LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint = new LoginUrlAuthenticationEntryPoint
                ("/getLogin");
        return loginUrlAuthenticationEntryPoint;
    }

    @Bean
    AuthenticationEntryPoint authenticationEntryPoint(){
        AuthenticationEntryPoint authenticationEntryPoint= (httpServletRequest, httpServletResponse, e) ->
        {
            httpServletResponse.setContentType("application/json;charset=utf-8");
            PrintWriter out = httpServletResponse.getWriter();
            ObjectMapper mapper = new ObjectMapper();
            Message message=new Message();
            out.write(mapper.writeValueAsString(message.error_login()));
            out.flush();
            out.close();
        };
        return authenticationEntryPoint;
    }
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().frameOptions().disable();//允许在iframe中加载页面
        http.csrf().disable();//禁用csrf,否则post请求无法提交,只能通过模板渲染
        http
            .authorizeRequests()
                //.antMatchers("/*/*").permitAll()
                .antMatchers("/admin/**").authenticated()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").authenticated()
                .antMatchers("/user/**").hasRole("USER")
                //.anyRequest().authenticated()
                .and()
            .formLogin()
                //此处写登录成功后的操作无效,userPasswordAuthenticationProcessingFilter()已接管此类设置
                .loginPage("/getLogin")
                .permitAll()
                .and()
            .logout()
                .permitAll()
                .and()
           .exceptionHandling()
                //已登录用户无权访问时的登录端点
                .accessDeniedHandler(((httpServletRequest, httpServletResponse, e) -> httpServletResponse.sendRedirect("/getLogin?forbidden")))
               //accessDeniedPage("/getLogin?forbidden")//无权访问返回该页面
                //配置未登录用户无权访问时的登录端点
               .authenticationEntryPoint(authenticationEntryPoint());

        //核心:添加过滤器,注意先后顺序
        http.addFilterBefore(userPasswordAuthenticationProcessingFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
    }

    //核心:配置管理器
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        //在管理器中添加provider
        auth.authenticationProvider(userPasswordAuthentiactionProvider());
    }
}