springboot+security实现登陆、权限管理

首先在sprintboot项目中引入SpringSecurity 依赖,如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

添加依赖后直接启动项目则会使用默认配置,在控制台会直接打印出默认用户(user)的密码(Using generated security password: xxxxx)此时在浏览器中输入项目地址测绘出现如下页面:

springboot 整合security 权限管理 springboot+security_ide

sprintboot Security 核心配置类WebSecurityConfigurerAdapter,继承该类 根据需求选择重写它的三个重载方法configure:

1、 重写configure(WebSecurity web)方法配置可以直接访问的资源:

@Override
    public void configure(WebSecurity web) throws Exception {
        // 给不需要权限能访问的资源
        web.ignoring().antMatchers("/index.html", "/static/**", "/favicon.ico");
    }

2、重写configure(AuthenticationManagerBuilder auth)配置判断依据:

a: 在代码中写死帐号和密码并使用官方提供的加密方式

// BCryptPasswordEncoder为官方提供的加密类
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().passwordEncoder(passwordEncoder)
        //设置admin用户的密码为‘123’,权限为‘admin'
        .withUser("admin").password(passwordEncoder.encode("123")).roles("ADMIN")
        .and()
        //设置test用户的密码为‘t123’,权限为‘user'
        .withUser("test").password(passwordEncoder.encode("t123")).roles("USER");
}

b: 从数据库读取用户和密码采用默认的方式校验和自定义加密方式

建立UserBean.java,需要实现UserDetails接口

package com.security.sprintsecurity.bean;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import lombok.Data;

@Data
public class User implements UserDetails{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String username;
	private String password;
    
    //账户权限集
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		// TODO Auto-generated method stub
        //我们返回的权限必须带有“ROLE_”开头才可以,spring会自己截取ROLE_后边的字符串
        //如下为用户的两个权限
        String role1="ROLE_role1";
        String role2="ROLE_role1";
        GrantedAuthority authority1=new SimpleGrantedAuthority(role1);
        GrantedAuthority authority2=new SimpleGrantedAuthority(role2);
        List<GrantedAuthority> list=new ArrayList<>();
        list.add(authority1);
        list.add(authority2);
		return list;
	}
    
    //账户是否未过期
	@Override
	public boolean isAccountNonExpired() {
		// TODO Auto-generated method stub
		return true;
	}
    
    //账户是否未锁定
	@Override
	public boolean isAccountNonLocked() {
		// TODO Auto-generated method stub
		return true;
	}
    
    //凭证是否未过期
	@Override
	public boolean isCredentialsNonExpired() {
		// TODO Auto-generated method stub
		return true;
	}
    
    //账户片是否可用
	@Override
	public boolean isEnabled() {
		// TODO Auto-generated method stub
		return true;
	}

}

建立UserService.java,需要实现UserDetailsService接口

package com.security.sprintsecurity.service;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.security.sprintsecurity.bean.UserBean;


@Service
public class UserService implements UserDetailsService {

    //在loadUserByUsername查询数据库
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// TODO Auto-generated method stub
		UserBean user = new UserBean();
        //此处可以从数据库读数据
		user.setUsername("ll");
		user.setPassword("123");
		return user;
	}

}

配置从数据库中读用户和密码以及自定义加密方式

@Autowired
UserService userService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //配置从数据库中读数据
    auth.userDetailsService(userService)
        //自定义加密方式需要实现PasswordEncoder接口
        .passwordEncoder(new PasswordEncoder() {
            //加密方式
            @Override
            public String encode(CharSequence rawPassword) {
                // TODO Auto-generated method stub
                return (String) rawPassword;
            }
            
			//密码比对
            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                // TODO Auto-generated method stub
                //encodedPassword为数据库中保存的加密密码
                //rawPassword为用户输入的密码
                return encode(rawPassword).equals(encodedPassword);
            }

        });
    }

c: 自定义验证方式,实现AuthenticationProvider接口

@Autowired
UserService userService;    	
//添加校验规则
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    // TODO Auto-generated method stub
    String userName = (String) authentication.getPrincipal(); //拿到username
    String password = (String) authentication.getCredentials(); //拿到password

    UserDetails userDetails = userService.selectUserByUsername(userName);//自定义得到用户信息的方法
    if (userDetails != null) {
        // 构建返回的用户登录成功的token
        return new UsernamePasswordAuthenticationToken(userDetails, password,userDetails.getAuthorities());
    }
    /*验证不通过*/
    return null;
}

3、重写configure(HttpSecurity http)方法配置登陆处理

a: 接受表单格式数据(a=1&b=1),即通过request.getParameter(”“)获取前端数据

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

    //接受表单格式数据(a=1&b=1),即通过request.getParameter(”“)获取
    http
        //.anonymous().disable()//禁止匿名,
        .csrf().disable() //关闭csrf
        .formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
        .loginPage("/login.html")//登陆页面路径
        .loginProcessingUrl("/login")//登陆提交数据路径
        .usernameParameter("username").passwordParameter("password")//设置表单中对应的用户名和密码参数名。默认为username和password
        //        .defaultSuccessUrl("")//认证成功后的默认跳转页面
        //        .failureUrl("")//登录失败转发的url,重定向后响应的Location中带有(“?error”)
        //        .failureForwardUrl("")//登录失败转发的url,重定向后响应的Location中不带有(“?error”)
        //        .successForwardUrl("")//登录成功转发的url
        .successHandler(new AuthenticationSuccessHandler() {//自定义对成功的处理
            private RequestCache requestCache = new HttpSessionRequestCache();
            private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                                Authentication authentication) throws IOException, ServletException {
                // TODO Auto-generated method stub

                //返回数据
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write("成功");

                //重定向
                //				SavedRequest savedRequest = requestCache.getRequest(request, response);
                //		        if (savedRequest == null){
                //		            redirectStrategy.sendRedirect(request, response, "/index");
                //		        }else {
                //		            redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl());
                //		        }
            }

        })
        .failureHandler(new AuthenticationFailureHandler() {//自定义对失败的处理

            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                                AuthenticationException exception) throws IOException, ServletException {
                // TODO Auto-generated method stub
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write(exception.getMessage());
            }

        })
        .permitAll()//表单登录,permitAll()表示不需要验证登录页面,登录失败页面
        .and()
        .logout()//退出登陆
        .logoutUrl("/logout")//退出登陆路径
//        .logoutSuccessUrl("/my/index")//登录退出转发的url
        .logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理返回
            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
                // TODO Auto-generated method stub
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write(authentication.getName());
            }

        })
        .permitAll()
        .and()
		.authorizeRequests().anyRequest().authenticated()//表示其他的都需要验证
         .and()
         .sessionManagement()//session管理
         .maximumSessions(1)//最大session 数量(一个用户的最大同时登陆数)
         .maxSessionsPreventsLogin(true)//超过最大sessin数量后时候阻止登录
//         .expiredSessionStrategy(null)//自定义session过期错略
//         .sessionRegistry(null)//自定义的session 注册表
         .expiredUrl("/");//会话失效后跳转的url

}

b: 接收json格式数据

需要继承UsernamePasswordAuthenticationFilter 类,并重写attemptAuthentication方法

package com.security.sprintsecurity;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.fasterxml.jackson.databind.ObjectMapper;

public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
                || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
            ObjectMapper mapper = new ObjectMapper();
            UsernamePasswordAuthenticationToken authRequest = null;
            try (InputStream is = request.getInputStream()) {
                Map<String,String> authenticationBean = mapper.readValue(is, Map.class);
                authRequest = new UsernamePasswordAuthenticationToken(
                        authenticationBean.get("username"), authenticationBean.get("password"));
            } catch (IOException e) {
                e.printStackTrace();
                authRequest = new UsernamePasswordAuthenticationToken(
                        "", "");
            } finally {
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            }
        }
        else {
            return super.attemptAuthentication(request, response);
        }
    }
}

重写configure(HttpSecurity http)方法

CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
       CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
       //登陆成功处理
       filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
           @Override
           public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
               resp.setContentType("application/json;charset=utf-8");
               PrintWriter out = resp.getWriter();
               out.write("成功");
               out.flush();
               out.close();
           }
       });
       //登陆失败处理
       filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
           @Override
           public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
               resp.setContentType("application/json;charset=utf-8");
               PrintWriter out = resp.getWriter();
               out.write("失败");
               out.flush();
               out.close();
           }
       });
       
     //登陆失败处理
       filter.setAuthenticationManager(authenticationManagerBean());
       return filter;
   }
   
   @Override
   protected void configure(HttpSecurity http) throws Exception {
	    http
		.formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
		.loginPage("/login.html")//登陆页面路径
		.loginProcessingUrl("/login")//登陆提交数据路径
		.permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
		.and().csrf().disable()
		.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
		.logout()//退出登陆
		.logoutUrl("/logout")//退出登陆路径
		//          .logoutSuccessUrl("/my/index")//登录退出转发的url
		.logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理返回
			@Override
			public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
			                            Authentication authentication) throws IOException, ServletException {
			      // TODO Auto-generated method stub
				response.setContentType("application/json;charset=utf-8");
		        response.getWriter().write(authentication.getName());
			}
		})
		.permitAll()
		.and()
		.authorizeRequests().anyRequest().authenticated();//表示其他的都需要验证
    }

4、 权限管理

a: 在代码中写死权限

@Override
protected void configure(HttpSecurity http) throws Exception {
http
    .formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
    .loginPage("/login.html")//登陆页面路径
    .loginProcessingUrl("/login")//登陆提交数据路径
    .permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
    .and().csrf().disable()
    .addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
    .logout()//退出登陆
    .logoutUrl("/logout")//退出登陆路径
    //          .logoutSuccessUrl("/my/index")//登录退出转发的url
    .logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
        @Override
        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                    Authentication authentication) throws IOException, ServletException {
            // TODO Auto-generated method stub
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(authentication.getName());
        }
    })
    .permitAll()
    //权限管理
    .and()
    .authorizeRequests()
    .antMatchers("/index").permitAll()//这就表示 /index这个页面不需要权限认证,所有人都可以访问
    .antMatchers("/admin").hasRole("admin") //这就表示/admin的这个资源需要有ROLE_admin的这个角色才能访问。不然就会提示拒绝访问
    .antMatchers(HttpMethod.POST,"/test/*").hasRole("u")//这就表示/test/路径下post请求的资源需要有ROLE_user的这个角色才能访问。不然就会提示拒绝访问
    .anyRequest().authenticated()//其他请求必须经过认证以后才能访问
    .and()
    .exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {//权限不足自定义处理
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response,
                           AccessDeniedException accessDeniedException) throws IOException, ServletException {
            // TODO Auto-generated method stub
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setContentType("application/json;charset=UTF-8");
            PrintWriter out = response.getWriter();
            out.write("权限错误");
            out.flush();
            out.close();
        }
	});      
}

b: 基于RBAC自定义全限控制

@Component("rbacService")
public class RbacServiceImpl {
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
               Object principal = authentication.getPrincipal();
               Collection<GrantedAuthority> urs = (Collection<GrantedAuthority>) authentication.getAuthorities();
               if (principal instanceof UserDetails) { //首先判断先当前用户是否是我们UserDetails对象。
                     List<String> rules = new ArrayList();
                     rules.add("ROLE_admin"); // 数据库读取该url所需要的权限
                     for (String rule : rules) {
                    	 for(GrantedAuthority ur: urs) {
                    		 if(String.valueOf(ur).equals(rule)) {
                    			 return true;
                    		 }
                    	 }
                     }
               }
               return false;
         }
   }

	   
   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http
		.formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
		.loginPage("/login.html")//登陆页面路径
		.loginProcessingUrl("/login")//登陆提交数据路径
		.permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
		.and().csrf().disable()
		.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
		.logout()//退出登陆
		.logoutUrl("/logout")//退出登陆路径
		//          .logoutSuccessUrl("/my/index")//登录退出转发的url
		.logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
			@Override
			public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
			                            Authentication authentication) throws IOException, ServletException {
			      // TODO Auto-generated method stub
				response.setContentType("application/json;charset=utf-8");
		        response.getWriter().write(authentication.getName());
			}
		})
		.permitAll()
		//权限管理
		.and()
		.authorizeRequests()
		.anyRequest().access("@rbacService.hasPermission(request,authentication)")//必须经过认证以后才能访问
        .and()
        .exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {//权限不足自定义处理

			@Override
			public void handle(HttpServletRequest request, HttpServletResponse response,
					AccessDeniedException accessDeniedException) throws IOException, ServletException {
				// TODO Auto-generated method stub
				response.setStatus(HttpServletResponse.SC_FORBIDDEN);
				response.setContentType("application/json;charset=UTF-8");
	            PrintWriter out = response.getWriter();
	            out.write("权限错误");
	            out.flush();
	            out.close();
			}
        });
    }

c: 自定义权限过滤器和自定义决策管理器实现权限控制,需要实现FilterInvocationSecurityMetadataSource和AccessDecisionManager类。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
        .loginPage("/login.html")//登陆页面路径
        .loginProcessingUrl("/login")//登陆提交数据路径
        .permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
        .and().csrf().disable()
        .addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        .logout()//退出登陆
        .logoutUrl("/logout")//退出登陆路径
        //          .logoutSuccessUrl("/my/index")//登录退出转发的url
        .logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
                // TODO Auto-generated method stub
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write(authentication.getName());
            }
        })
        .permitAll()
        //权限管理
        .and()
        .authorizeRequests()
        .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {

            @Override
            public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                // TODO Auto-generated method stub
                //返回本次访问url需要的权限,可以有多个权限
                object.setSecurityMetadataSource(new FilterInvocationSecurityMetadataSource() {

                    @Override
                    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
                        // TODO Auto-generated method stub
                        //						String requestUrl = ((FilterInvocation) object).getRequestUrl();
                        String[] rules = {"ROLE_admin"};
                        return SecurityConfig.createList(rules);
                    }


                    //此处方法如果做了实现,返回了定义的权限资源列表,
                    //Spring Security会在启动时校验每个ConfigAttribute是否配置正确,
                    //如果不需要校验,这里实现方法,方法体直接返回null即可。
                    @Override
                    public Collection<ConfigAttribute> getAllConfigAttributes() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    //方法返回类对象是否支持校验,
                    //web项目一般使用FilterInvocation来判断,或者直接返回true
                    @Override
                    public boolean supports(Class<?> clazz) {
                        // TODO Auto-generated method stub
                        return FilterInvocation.class.isAssignableFrom(clazz);
                    }

                });
                object.setAccessDecisionManager(new AccessDecisionManager() {

                    @Override
                    public void decide(Authentication authentication, Object object,
                                       Collection<ConfigAttribute> configAttributes)
                        throws AccessDeniedException, InsufficientAuthenticationException {
                        // TODO Auto-generated method stub
                        if (authentication == null) {
                            throw new AccessDeniedException("当前访问没有权限");
                        }
                        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
                        while (iterator.hasNext()) {
                            ConfigAttribute ca = iterator.next();
                            //当前请求需要的权限
                            String needRole = ca.getAttribute();
                            //当前用户所具有的权限
                            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                            for (GrantedAuthority authority : authorities) {
                                if (authority.getAuthority().equals(needRole)) {
                                    return;
                                }
                            }
                        }
                        throw new AccessDeniedException("当前访问没有权限");
                    }

                    @Override
                    public boolean supports(ConfigAttribute attribute) {
                        // TODO Auto-generated method stub
                        return false;
                    }

                    @Override
                    public boolean supports(Class<?> clazz) {
                        // TODO Auto-generated method stub
                        return false;
                    }

                });
                return object;
            }


        })
        .and()
        .exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {//权限不足自定义处理

        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response,
                           AccessDeniedException accessDeniedException) throws IOException, ServletException {
            // TODO Auto-generated method stub
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setContentType("application/json;charset=UTF-8");
            PrintWriter out = response.getWriter();
            out.write("权限错误");
            out.flush();
            out.close();
        }
    });
}

5、记住我功能

@Autowired
private DruidDataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
    tokenRepository.setDataSource(dataSource);
    tokenRepository.setCreateTableOnStartup(true); //系统在启动的时候生成“记住我”的数据表(只能使用一次)
    return tokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {

    //接受表单格式数据(a=1&b=1),即通过request.getParameter(”“)获取
    http
        //           .anonymous().disable()//禁止匿名,
        .csrf().disable() //关闭csrf
        .formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
        .loginPage("/login.html")//登陆页面路径
        .loginProcessingUrl("/login")//登陆提交数据路径
        //           .usernameParameter("username").passwordParameter("password")//设置表单中对应的用户名和密码参数名。默认为username和password
        //        .defaultSuccessUrl("")//认证成功后的默认跳转页面
        //        .failureUrl("")//登录失败转发的url,重定向后响应的Location中带有(“?error”)
        //        .failureForwardUrl("")//登录失败转发的url,重定向后响应的Location中不带有(“?error”)
        //        .successForwardUrl("")//登录成功转发的url
        .permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
        .successHandler(new AuthenticationSuccessHandler() {//自定义对成功的处理
            private RequestCache requestCache = new HttpSessionRequestCache();
            private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                                Authentication authentication) throws IOException, ServletException {
                // TODO Auto-generated method stub

                //返回数据
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write("成功");

                //重定向
                //				SavedRequest savedRequest = requestCache.getRequest(request, response);
                //		        if (savedRequest == null){
                //		            redirectStrategy.sendRedirect(request, response, "/index");
                //		        }else {
                //		            redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl());
                //		        }
            }

        })
        .failureHandler(new AuthenticationFailureHandler() {//自定义对失败的处理

            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                                AuthenticationException exception) throws IOException, ServletException {
                // TODO Auto-generated method stub
                response.setContentType("application/json;charset=utf-8");

                String msg = "";
                if (exception instanceof BadCredentialsException ||
                    exception instanceof UsernameNotFoundException) {
                    msg = "账户名或者密码输入错误!";
                } else if (exception instanceof LockedException) {
                    msg = "账户被锁定,请联系管理员!";
                } else if (exception instanceof CredentialsExpiredException) {
                    msg = "密码过期,请联系管理员!";
                } else if (exception instanceof AccountExpiredException) {
                    msg = "账户过期,请联系管理员!";
                } else if (exception instanceof DisabledException) {
                    msg = "账户被禁用,请联系管理员!";
                } else {
                    msg = "登录失败!";
                }
                response.setStatus(401);
                response.getWriter().write(msg);
            }

        })
        .and()
        .logout()
        .logoutUrl("/logout")
        .logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
                // TODO Auto-generated method stub
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write(authentication.getName());
            }

        })
        .and()
        .authorizeRequests().anyRequest().authenticated()//表示其他的都需要验证
        .and()
        .sessionManagement()//session管理
        .maximumSessions(1)//最大session 数量(一个用户的最大同时登陆数)
        .maxSessionsPreventsLogin(true)//超过最大sessin数量后时候阻止登录
        //           .expiredSessionStrategy(null)//自定义session过期错略
        //           .sessionRegistry(null)//自定义的session 注册表
        .expiredUrl("/login.html")//会话失效后跳转的url
        .and()
        .rememberMe()
        //           .rememberMeParameter("remember-me")//记住我功能在表单中的key
        .userDetailsService(userService)//使用userDetailsService用Token从数据库中获取用户自动登录
        .tokenRepository(persistentTokenRepository())//持久化token
        .tokenValiditySeconds(60);//设置Token的有效时间
}

WebSecurityConfigurer配置类的所有代码

package com.security.sprintsecurity.config;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.alibaba.druid.pool.DruidDataSource;
import com.security.sprintsecurity.service.UserService;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

	@Autowired
	UserService userService;
	
	
   @Override
    public void configure(WebSecurity web) throws Exception {
	// 给不需要权限能访问的资源
        web.ignoring().antMatchers("/index.html", "/js/*", "/image/*", "/css/*", "/fonts/*", "/favicon.ico");
    }
	   
   BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
   
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       //配置从数据库中读数据
       auth.authenticationProvider(new AuthenticationProvider() {
    	   
    	//自定义验证方式,实现AuthenticationProvider接口
		@Override
		public Authentication authenticate(Authentication authentication) throws AuthenticationException {
			// TODO Auto-generated method stub
			String userName = (String) authentication.getPrincipal(); //拿到username
	        String password = (String) authentication.getCredentials(); //拿到password
	       
	        UserDetails userDetails = userService.selectUserByUsername(userName);//自定义得到用户信息的方法
	    	//添加校验规则
	        if (userDetails != null) {
	        	// 构建返回的用户登录成功的token
	            return new UsernamePasswordAuthenticationToken(userDetails, password,userDetails.getAuthorities());
	        }
	        /*验证不通过*/
	        return null;
		}

		
		@Override
		public boolean supports(Class<?> authentication) {
			// TODO Auto-generated method stub
			//如果该AuthenticationProvider支持传入的Authentication对象,则返回true
			return authentication.equals(UsernamePasswordAuthenticationToken.class);
		}
    	   
       });
       
//       //从数据库读取用户和密码采用默认的方式校验和自定义加密方式
//       auth.userDetailsService(userService)
//           //自定义加密方式需要实现PasswordEncoder接口
//           .passwordEncoder(new PasswordEncoder() {
//               //加密方式
//               @Override
//               public String encode(CharSequence rawPassword) {
//                   // TODO Auto-generated method stub
//                   return (String) rawPassword;
//               }
//               
//   			//密码比对
//               @Override
//               public boolean matches(CharSequence rawPassword, String encodedPassword) {
//                   // TODO Auto-generated method stub
//                   //encodedPassword为数据库中保存的加密密码
//                   //rawPassword为用户输入的密码
//                   return encode(rawPassword).equals(encodedPassword);
//               }
//
//           });
       
//       //在代码中写死帐号和密码并使用官方提供的加密方式
//		auth.inMemoryAuthentication().passwordEncoder(passwordEncoder)
//        	.withUser("admin").password(passwordEncoder.encode("123")).roles("ADMIN")
//        	.and()
//        	.withUser("test").password(passwordEncoder.encode("t123")).roles("USER");
    }

	
   CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
       CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
       //登陆成功处理
       filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
           @Override
           public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
               resp.setContentType("application/json;charset=utf-8");
               PrintWriter out = resp.getWriter();
               out.write("成功");
               out.flush();
               out.close();
           }
       });
       //登陆失败处理
       filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
           @Override
           public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
               resp.setContentType("application/json;charset=utf-8");
               PrintWriter out = resp.getWriter();
               out.write("失败");
               out.flush();
               out.close();
           }
       });
       
     //登陆失败处理
       filter.setAuthenticationManager(authenticationManagerBean());
       return filter;
   }
   
   public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
	    @Override
	    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
	        if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
	                || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
	            ObjectMapper mapper = new ObjectMapper();
	            UsernamePasswordAuthenticationToken authRequest = null;
	            try (InputStream is = request.getInputStream()) {
	                Map<String,String> authenticationBean = mapper.readValue(is, Map.class);
	                authRequest = new UsernamePasswordAuthenticationToken(
	                        authenticationBean.get("username"), authenticationBean.get("password"));
	            } catch (IOException e) {
	                e.printStackTrace();
	                authRequest = new UsernamePasswordAuthenticationToken(
	                        "", "");
	            } finally {
	                setDetails(request, authRequest);
	                return this.getAuthenticationManager().authenticate(authRequest);
	            }
	        }
	        else {
	            return super.attemptAuthentication(request, response);
	        }
	    }
	}
   
   @Component("rbacService")
   public class RbacServiceImpl {
         public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
               Object principal = authentication.getPrincipal();
               Collection<GrantedAuthority> urs = (Collection<GrantedAuthority>) authentication.getAuthorities();
               if (principal instanceof UserDetails) { //首先判断先当前用户是否是我们UserDetails对象。
                     List<String> rules = new ArrayList();
                     rules.add("ROLE_admin"); // 数据库读取该url所需要的权限
                     for (String rule : rules) {
                    	 for(GrantedAuthority ur: urs) {
                    		 if(String.valueOf(ur).equals(rule)) {
                    			 return true;
                    		 }
                    	 }
                     }
               }
               return false;
         }
   }
   
   @Autowired
   private DruidDataSource dataSource;
   
   @Bean
   public PersistentTokenRepository persistentTokenRepository() {
       JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
       tokenRepository.setDataSource(dataSource);
       tokenRepository.setCreateTableOnStartup(true); //系统在启动的时候生成“记住我”的数据表(只能使用一次)
       return tokenRepository;
   }
   
   
   @Override
   protected void configure(HttpSecurity http) throws Exception {

       //接受表单格式数据(a=1&b=1),即通过request.getParameter(”“)获取
       http
//           .anonymous().disable()//禁止匿名,
           .csrf().disable() //关闭csrf
           .formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
           .loginPage("/login.html")//登陆页面路径
           .loginProcessingUrl("/login")//登陆提交数据路径
//           .usernameParameter("username").passwordParameter("password")//设置表单中对应的用户名和密码参数名。默认为username和password
           //        .defaultSuccessUrl("")//认证成功后的默认跳转页面
           //        .failureUrl("")//登录失败转发的url,重定向后响应的Location中带有(“?error”)
           //        .failureForwardUrl("")//登录失败转发的url,重定向后响应的Location中不带有(“?error”)
           //        .successForwardUrl("")//登录成功转发的url
           .permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
           .successHandler(new AuthenticationSuccessHandler() {//自定义对成功的处理
               private RequestCache requestCache = new HttpSessionRequestCache();
               private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

               @Override
               public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                                   Authentication authentication) throws IOException, ServletException {
                   // TODO Auto-generated method stub

                   //返回数据
                   response.setContentType("application/json;charset=utf-8");
                   response.getWriter().write("成功");

                   //重定向
                   //				SavedRequest savedRequest = requestCache.getRequest(request, response);
                   //		        if (savedRequest == null){
                   //		            redirectStrategy.sendRedirect(request, response, "/index");
                   //		        }else {
                   //		            redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl());
                   //		        }
               }

           })
           .failureHandler(new AuthenticationFailureHandler() {//自定义对失败的处理

               @Override
               public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                                   AuthenticationException exception) throws IOException, ServletException {
                   // TODO Auto-generated method stub
                   response.setContentType("application/json;charset=utf-8");
                   
                   String msg = "";
                   if (exception instanceof BadCredentialsException ||
                           exception instanceof UsernameNotFoundException) {
                	   msg = "账户名或者密码输入错误!";
                   } else if (exception instanceof LockedException) {
                	   msg = "账户被锁定,请联系管理员!";
                   } else if (exception instanceof CredentialsExpiredException) {
                	   msg = "密码过期,请联系管理员!";
                   } else if (exception instanceof AccountExpiredException) {
                	   msg = "账户过期,请联系管理员!";
                   } else if (exception instanceof DisabledException) {
                	   msg = "账户被禁用,请联系管理员!";
                   } else {
                	   msg = "登录失败!";
                   }
                   response.setStatus(401);
                   response.getWriter().write(msg);
               }

           })
           .and()
           .logout()
           .logoutUrl("/logout")
           .logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
               @Override
               public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                           Authentication authentication) throws IOException, ServletException {
                   // TODO Auto-generated method stub
                   response.setContentType("application/json;charset=utf-8");
                   response.getWriter().write(authentication.getName());
               }

           })
           .and()
           .rememberMe()
//           .rememberMeParameter("remember-me")//记住我功能在表单中的key
           .userDetailsService(userService)//使用userDetailsService用Token从数据库中获取用户自动登录
           .tokenRepository(persistentTokenRepository())//持久化token
           .tokenValiditySeconds(60)//设置Token的有效时间
           .and()
           .authorizeRequests().anyRequest().authenticated()//表示其他的都需要验证
           .and()
           .sessionManagement()//session管理
           .maximumSessions(1)//最大session 数量(一个用户的最大同时登陆数)
           .maxSessionsPreventsLogin(true)//超过最大sessin数量后时候阻止登录
//           .expiredSessionStrategy(null)//自定义session过期错略
//           .sessionRegistry(null)//自定义的session 注册表
           .expiredUrl("/login.html");//会话失效后跳转的url

       
       //获取json或者表单数据
//	    http
//		.formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
//		.loginPage("/login.html")//登陆页面路径
//		.loginProcessingUrl("/login")//登陆提交数据路径
//		.permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
//		.and().csrf().disable()
//		.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
//		.logout()//退出登陆
//		.logoutUrl("/logout")//退出登陆路径
//		//          .logoutSuccessUrl("/my/index")//登录退出转发的url
//		.logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
//			@Override
//			public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
//			                            Authentication authentication) throws IOException, ServletException {
//			      // TODO Auto-generated method stub
//				response.setContentType("application/json;charset=utf-8");
//		        response.getWriter().write(authentication.getName());
//			}
//		})
//		.permitAll()
//		//权限管理
//		.and()
//		.authorizeRequests()
        .antMatchers("/index").permitAll()//这就表示 /index这个页面不需要权限认证,所有人都可以访问            
		.antMatchers("/admin").hasRole("admin") //这就表示/admin的这个资源需要有ROLE_admin的这个角色才能访问。不然就会提示拒绝访问
		.antMatchers(HttpMethod.POST,"/test/*").hasRole("u")//这就表示/test/路径下post请求的资源需要有ROLE_user的这个角色才能访问。不然就会提示拒绝访问
		.anyRequest().authenticated()//必须经过认证以后才能访问
		.anyRequest().access("@rbacService.hasPermission(request,authentication)")//必须经过认证以后才能访问
//		.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
//
//			@Override
//			public <O extends FilterSecurityInterceptor> O postProcess(O object) {
//				// TODO Auto-generated method stub
//				//返回本次访问url需要的权限,可以有多个权限
//				object.setSecurityMetadataSource(new FilterInvocationSecurityMetadataSource() {
//
//					@Override
//					public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//						// TODO Auto-generated method stub
						String requestUrl = ((FilterInvocation) object).getRequestUrl();
//						String[] rules = {"ROLE_admin"};
//						return SecurityConfig.createList(rules);
//					}
//
//					
//					 //此处方法如果做了实现,返回了定义的权限资源列表,
//				     //Spring Security会在启动时校验每个ConfigAttribute是否配置正确,
//				     //如果不需要校验,这里实现方法,方法体直接返回null即可。
//					@Override
//					public Collection<ConfigAttribute> getAllConfigAttributes() {
//						// TODO Auto-generated method stub
//						return null;
//					}
//
//					 //方法返回类对象是否支持校验,
//				     //web项目一般使用FilterInvocation来判断,或者直接返回true
//					@Override
//					public boolean supports(Class<?> clazz) {
//						// TODO Auto-generated method stub
//						return FilterInvocation.class.isAssignableFrom(clazz);
//					}
//					
//				});
//				object.setAccessDecisionManager(new AccessDecisionManager() {
//
//					@Override
//					public void decide(Authentication authentication, Object object,
//							Collection<ConfigAttribute> configAttributes)
//							throws AccessDeniedException, InsufficientAuthenticationException {
//						// TODO Auto-generated method stub
//						if (authentication == null) {
//			                throw new AccessDeniedException("当前访问没有权限");
//			            }
//					       Iterator<ConfigAttribute> iterator = configAttributes.iterator();
//					        while (iterator.hasNext()) {
//					            ConfigAttribute ca = iterator.next();
//					            //当前请求需要的权限
//					            String needRole = ca.getAttribute();
//					            //当前用户所具有的权限
//					            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//					            for (GrantedAuthority authority : authorities) {
//					                if (authority.getAuthority().equals(needRole)) {
//					                    return;
//					                }
//					            }
//					        }
//					        throw new AccessDeniedException("当前访问没有权限");
//					}
//
//					@Override
//					public boolean supports(ConfigAttribute attribute) {
//						// TODO Auto-generated method stub
//						return false;
//					}
//
//					@Override
//					public boolean supports(Class<?> clazz) {
//						// TODO Auto-generated method stub
//						return false;
//					}
//					
//				});
//                return object;
//			}
//
//			
//		})
//        .and()
//        .exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {//权限不足自定义处理
//
//			@Override
//			public void handle(HttpServletRequest request, HttpServletResponse response,
//					AccessDeniedException accessDeniedException) throws IOException, ServletException {
//				// TODO Auto-generated method stub
//				response.setStatus(HttpServletResponse.SC_FORBIDDEN);
//				response.setContentType("application/json;charset=UTF-8");
//	            PrintWriter out = response.getWriter();
//	            out.write("权限错误");
//	            out.flush();
//	            out.close();
//			}
//        });
    }
}