SpringSecurity 多种登录方式

6,507 阅读4分钟

前言

网上用的比较多的方式是UserDetailsService方案,这种方案难以实现多种登录方式。本文用Filter+Manager+Provider+Token的方案实现多种登录方式。代码过多,本文只展示其中一种登录方式。 简单描述下这几个东西的作用:

  • Filter负责拦截请求并调用Manager
  • Manager负责管理多个Provider,并选择合适的Provider进行认证
  • Provider负责认证,检查账号密码之类的
  • Token是认证信息,包含账号密码,具体可以自己定义

Filter代码

先看Filter的代码

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;
        }
    }
}

Token代码

Token的核心在于两个构造方法,一个是认证前使用,一个是认证后使用。

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;
    }

}

Provider代码

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));
    }
}

Security配置

主要看核心配置,在注释上标注出来了。其他登录端点,失败处理等根据自己需求改就行,没必要一模一样。

@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());
    }
}