相关配置类:SecurityConfig继承WebSecurityConfigurerAdapter(security配置类)、JwtAuthenticationTokenFilter(登录验证filter)、RestfulAccessDeniedHandler(自定义未授权拦截器)、RestAuthenticationEntryPoint(自定义未登录拦截器)、
其他相关类:UserDetails用户信息类、UserDetailsService用户信息接口实现类、UrlVoter自定义AccessDecisionVoter类
security配置类SecurityConfig
import com.wgs.blog.filter.JwtAuthenticationTokenFilter; import com.wgs.blog.interceptor.RestAuthenticationEntryPoint; import com.wgs.blog.interceptor.RestfulAccessDeniedHandler; import com.wgs.blog.service.UserDetailsServiceImpl; import com.wgs.blog.utils.UrlVoter; 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.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.vote.AuthenticatedVoter; import org.springframework.security.access.vote.RoleVoter; import org.springframework.security.access.vote.UnanimousBased; 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.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.access.expression.WebExpressionVoter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.Arrays; import java.util.List; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsServiceImpl userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { //使用下方cors配置,默认读取名为corsConfigurationSource的bean http.authorizeRequests() .antMatchers(HttpMethod.GET, // 允许对于网站静态资源的无授权访问 "/", "/*.html", "/favicon.ico", "/**/*.html", "/swagger-ui.html", "/**/*.css", "/**/*.js", "/swagger-resources/**", "/v2/api-docs/**", "/webjars/**", "/csrf" ) .permitAll() .antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求 .permitAll() .antMatchers("/login", "/register")// 对登录注册要允许匿名访问 .permitAll() .anyRequest()// 除上面外的所有请求全部需要鉴权认证 .authenticated() //自定义决策管理器accessDecisionManager,使用自定义AccessDecisionVoter //.accessDecisionManager(accessDecisionManager()) .and().cors(); //访问 /logout 表示用户注销,并清空session http.logout().logoutSuccessUrl("/logoutSuccess"); // 禁用缓存 http.headers().cacheControl(); //关闭csrf防护 http.sessionManagement()// 基于token,所以不需要session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .csrf().disable() // 添加JWT filter .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); //添加自定义未授权和未登录结果返回 http.exceptionHandling() .accessDeniedHandler(restfulAccessDeniedHandler()) .authenticationEntryPoint(restAuthenticationEntryPoint()); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } /** * 密码加密 * * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 跨域设置 * * @return */ @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:8080")); corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfiguration); return source; } @Bean public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() { return new JwtAuthenticationTokenFilter(); } @Bean public RestfulAccessDeniedHandler restfulAccessDeniedHandler() { return new RestfulAccessDeniedHandler(); } @Bean public RestAuthenticationEntryPoint restAuthenticationEntryPoint() { return new RestAuthenticationEntryPoint(); } @Bean public AccessDecisionManager accessDecisionManager() { List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList( new WebExpressionVoter(), new RoleVoter(), new AuthenticatedVoter(), new UrlVoter()); return new UnanimousBased(decisionVoters); } }
token登录验证JwtAuthenticationTokenFilter
import com.wgs.blog.service.UserDetailsServiceImpl; import com.wgs.blog.utils.JwtTokenUtil; import com.wgs.blog.utils.ValidatorUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private UserDetailsServiceImpl userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Value("token") private String tokenHead; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { //String token = httpServletRequest.getHeader("token"); String uri = httpServletRequest.getRequestURI(); //获取key值是this.tokenHead的header String authToken = httpServletRequest.getHeader(this.tokenHead); // String url = httpServletRequest.getRequestURL().toString(); System.out.println("uri:" + uri); System.out.println("authToken:" + authToken); String username = null; if(ValidatorUtils.empty(authToken)){ authToken = httpServletRequest.getParameter(this.tokenHead); } if(authToken != null) { username = jwtTokenUtil.getUserNameFromToken(authToken); if (uri.contains("login") || uri.contains("logout")) { if (uri.contains("logout")) { System.out.println("logout"); SecurityContextHolder.getContext().setAuthentication(null); } } else { if (null != username && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } } filterChain.doFilter(httpServletRequest, httpServletResponse); } }
RestfulAccessDeniedHandler(自定义未授权拦截器)
import com.wgs.blog.utils.JsonUtil; import com.wgs.blog.utils.ResultData; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 当访问接口没有权限时,自定义的返回结果 * https://github.com/shenzhuan/mallplus on 2018/4/26. */ public class RestfulAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("application/json"); httpServletResponse.getWriter().println(JsonUtil.objectToJson(new ResultData().forbidden(e.getMessage()))); httpServletResponse.getWriter().flush(); } }
RestAuthenticationEntryPoint(自定义未登录拦截器)
import com.wgs.blog.utils.JsonUtil; import com.wgs.blog.utils.ResultData; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 当未登录或者token失效访问接口时,自定义的返回结果 * https://github.com/shenzhuan/mallplus on 2018/5/14. */ public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("application/json"); httpServletResponse.getWriter().println(JsonUtil.objectToJson(new ResultData().unauthorized("暂未登录或token已经过期"))); httpServletResponse.getWriter().flush(); } }
需要权限访问的路径较少且简单时,在接口方法上使用@PreAuthorize("hasAuthority('user')")注解配置访问该路径需要的权限;
当需要权限访问的路径较多且复杂时可自定义AccessDecisionVoter。
配置中添加自定义的accessDecisionManager
自定义accessDecisionManager
自定义AccessDecisionVoter:
import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; import org.springframework.security.web.FilterInvocation; import java.util.Collection; public class UrlVoter implements AccessDecisionVoter<Object> { @Override public boolean supports(ConfigAttribute configAttribute) { return true; } @Override public boolean supports(Class<?> aClass) { return true; } @Override public int vote(Authentication authentication, Object o, Collection<ConfigAttribute> collection) { int result = -1; //写规则,返回值result值为-1/0/1,-1拒绝访问,0弃权,1允许访问 //String requestUrl = ((FilterInvocation)o).getRequestUrl(); //String url = requestUrl.substring(0,requestUrl.indexOf("?")>0?requestUrl.indexOf("?"):requestUrl.length()); return result; } }