赞
踩
Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实上的标准。
Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足自定义要求。
本篇博客详细记录了
SpringBoot整合SpringSecurity前后端分离项目的后端代码细节,并将完整项目上传至gitHub(更改数据源配置即可运行)。
项目完整功能:
GitHub: link. 欢迎star
友情链接:SpringBoot整合SpringSecurity+JWT实现单点登录
maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
1.登录拦截全局配置
@Configuration public class WebSecurityConfigure extends WebSecurityConfigurerAdapter { @Resource private UrlAuthenticationEntryPoint authenticationEntryPoint; //自定义未登录时:返回状态码401 @Resource private UrlAuthenticationSuccessHandler authenticationSuccessHandler; //自定义登录成功处理器:返回状态码200 @Resource private UrlAuthenticationFailureHandler authenticationFailureHandler; //自定义登录失败处理器:返回状态码402 @Resource private UrlAccessDeniedHandler accessDeniedHandler; //自定义权限不足处理器:返回状态码403 @Resource private UrlLogoutSuccessHandler logoutSuccessHandler; //自定义注销成功处理器:返回状态码200 @Resource private SelfAuthenticationProvider authenticationProvider; //自定义登录认证 @Resource private SelfFilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource; //动态获取url权限配置 @Resource private SelfAccessDecisionManager accessDecisionManager; //自定义权限判断管理器 @Resource private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource; //身份验证详细信息源 @Override public void configure(WebSecurity web) { // web.ignoring().antMatchers("/connect/**"); //无条件允许访问 web.ignoring().antMatchers("/common/**"); //无条件允许访问 } @Override protected void configure(AuthenticationManagerBuilder auth) { auth.authenticationProvider(authenticationProvider); //自定义登录认证 } @Override protected void configure(HttpSecurity http) throws Exception { // 关闭csrf验证(防止跨站请求伪造攻击) http.csrf().disable(); // 未登录时:返回状态码401 http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint); // 无权访问时:返回状态码403 http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // url权限认证处理 http.antMatcher("/**").authorizeRequests() // .antMatchers("/security/user/**").hasRole("ADMIN") //需要ADMIN角色才可以访问 // .antMatchers("/connect").hasIpAddress("127.0.0.1") //只有ip[127.0.0.1]可以访问'/connect'接口 .anyRequest() //其他任何请求 .authenticated() //都需要身份认证 .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O o) { o.setSecurityMetadataSource(filterInvocationSecurityMetadataSource); //动态获取url权限配置 o.setAccessDecisionManager(accessDecisionManager); //权限判断 return o; } }); // 将session策略设置为无状态的,通过token进行权限认证 // http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 开启自动配置的登录功能 http.formLogin() //开启登录 // .loginPage("/login") //登录页面(前后端不分离) .loginProcessingUrl("/nonceLogin") //自定义登录请求路径(post) .usernameParameter("username").passwordParameter("password") //自定义登录用户名密码属性名,默认为username和password // .successForwardUrl("/index") //登录成功后的url(post,前后端不分离) // .failureForwardUrl("/error") //登录失败后的url(post,前后端不分离) .successHandler(authenticationSuccessHandler) //验证成功处理器(前后端分离):返回状态码200 .failureHandler(authenticationFailureHandler) //验证失败处理器(前后端分离):返回状态码402 .authenticationDetailsSource(authenticationDetailsSource); //身份验证详细信息源(登录验证中增加额外字段) // 开启自动配置的注销功能 http.logout() //用户注销, 清空session .logoutUrl("/nonceLogout") //自定义注销请求路径 // .logoutSuccessUrl("/bye") //注销成功后的url(前后端不分离) .logoutSuccessHandler(logoutSuccessHandler); //注销成功处理器(前后端分离):返回状态码200 } }
2.(1)自定义登录认证
@Slf4j @Component public class SelfAuthenticationProvider implements AuthenticationProvider { @Resource private SelfUserDetailsService selfUserDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { log.info("authentication >> {}", JSONObject.toJSONString(authentication, SerializerFeature.WriteMapNullValue)); CustomWebAuthenticationDetails customWebAuthenticationDetails = (CustomWebAuthenticationDetails) authentication.getDetails(); //获取身份验证详细信息 // String remoteAddress = customWebAuthenticationDetails.getRemoteAddress(); // String sessionId = customWebAuthenticationDetails.getSessionId(); // System.out.println("remoteAddress >> " + remoteAddress); // System.out.println("sessionId >> " + sessionId); // System.out.println("details >> " + JSONObject.toJSONString(customWebAuthenticationDetails, SerializerFeature.WriteMapNullValue)); System.out.println("macAddress >> " + customWebAuthenticationDetails.getMacAddress()); //用于校验mac地址白名单(这里只是打个比方,登录验证中增加的额外字段) String username = (String) authentication.getPrincipal(); //表单输入的用户名 String password = (String) authentication.getCredentials(); //表单输入的密码 UserDetails userInfo = selfUserDetailsService.loadUserByUsername(username); boolean matches = new BCryptPasswordEncoder().matches(password, userInfo.getPassword()); //校验用户名密码 if (!matches) { throw new BadCredentialsException("The password is incorrect!!"); } return new UsernamePasswordAuthenticationToken(username, userInfo.getPassword(), userInfo.getAuthorities()); } @Override public boolean supports(Class<?> aClass) { return true; } }
2.(2)自定义用户认证
@Component public class SelfUserDetailsService implements UserDetailsService { @Resource private UserService userService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SelfUserDetails userInfo = new SelfUserDetails(); userInfo.setUsername(username); //任意登录用户名 String password = userService.findPasswordByUsernameAfterValidTime(username); if (ObjectUtils.isEmpty(password)) { throw new UsernameNotFoundException("User name" + username + "not find!!"); } userInfo.setPassword(password); //从数据库获取密码 Set<SimpleGrantedAuthority> authoritiesSet = new HashSet<>(); List<String> roles = userService.findRoleNameByUsername(username); for (String roleName : roles) { SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(roleName); //用户拥有的角色 authoritiesSet.add(simpleGrantedAuthority); } userInfo.setAuthorities(authoritiesSet); return userInfo; } }
2.(3)自定义web身份验证详细信息(用于登录验证中增加额外参数)
class CustomWebAuthenticationDetails extends WebAuthenticationDetails implements Serializable { private String macAddress; CustomWebAuthenticationDetails(HttpServletRequest httpServletRequest) { super(httpServletRequest); // Enumeration<String> headerNames = httpServletRequest.getHeaderNames(); // while (headerNames.hasMoreElements()) { // String s = headerNames.nextElement(); // String header = httpServletRequest.getHeader(s); // System.out.println(s + ": " + header); // } macAddress = httpServletRequest.getParameter("macAddress"); } String getMacAddress() { return macAddress; } }
2.(4)自定义身份验证详细信息源
@Component
public class CustomAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
@Override
public WebAuthenticationDetails buildDetails(HttpServletRequest httpServletRequest) {
return new CustomWebAuthenticationDetails(httpServletRequest);
}
}
3.动态获取url权限配置
@Slf4j @Component public class SelfFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Resource private UserService userService; private AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException { Set<ConfigAttribute> set = new HashSet<>(); // 获取请求地址 String requestUrl = ((FilterInvocation) o).getRequestUrl(); log.info("requestUrl >> {}", requestUrl); List<String> menuUrl = userService.findAllMenuUrl(); for (String url : menuUrl) { if (antPathMatcher.match(url, requestUrl)) { List<String> roleNames = userService.findRoleNameByMenuUrl(url); //当前请求需要的权限 roleNames.forEach(roleName -> { SecurityConfig securityConfig = new SecurityConfig(roleName); set.add(securityConfig); }); } } if (ObjectUtils.isEmpty(set)) { return SecurityConfig.createList("ROLE_LOGIN"); } return set; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> aClass) { return FilterInvocation.class.isAssignableFrom(aClass); } }
4.自定义权限判断管理器
@Slf4j @Component public class SelfAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException { // 当前请求需要的权限 log.info("collection:{}", collection); // 当前用户所具有的权限 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); log.info("principal:{} authorities:{}", authentication.getPrincipal().toString(), authorities); for (ConfigAttribute configAttribute : collection) { // 当前请求需要的权限 String needRole = configAttribute.getAttribute(); if ("ROLE_LOGIN".equals(needRole)) { if (authentication instanceof AnonymousAuthenticationToken) { throw new BadCredentialsException("Not logged in!!"); } else { return; } } // 当前用户所具有的权限 for (GrantedAuthority grantedAuthority : authorities) { // 包含其中一个角色即可访问 if (grantedAuthority.getAuthority().equals(needRole)) { return; } } } throw new AccessDeniedException("SimpleGrantedAuthority!!"); } @Override public boolean supports(ConfigAttribute configAttribute) { return true; } @Override public boolean supports(Class<?> aClass) { return true; } }
5.自定义未登录时:返回状态码401
@Component public class UrlAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { UrlResponse response = new UrlResponse(); response.setSuccess(false); response.setCode("401"); response.setMessage(e.getMessage()); response.setData(null); httpServletResponse.setStatus(401); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("text/html;charset=UTF-8"); httpServletResponse.getWriter().write(GsonUtil.GSON.toJson(response)); } }
6.自定义登录成功处理器:返回状态码200
@Component public class UrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Resource private UserService userService; @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.setCharacterEncoding("UTF-8"); UrlResponse response = new UrlResponse(); response.setSuccess(true); response.setCode("200"); response.setMessage("Login Success!"); String username = (String) authentication.getPrincipal(); //表单输入的用户名 String password = (String) authentication.getCredentials(); //表单输入的密码 Map<String, Object> userInfo = userService.findMenuInfoByUsername(username, response); response.setData(userInfo); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("text/html;charset=UTF-8"); httpServletResponse.getWriter().write(GsonUtil.GSON.toJson(response)); } }
7.自定义登录失败处理器:返回状态码402
@SuppressWarnings("Duplicates") @Component public class UrlAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { UrlResponse response = new UrlResponse(); response.setSuccess(false); response.setCode("402"); response.setMessage(e.getMessage()); response.setData(null); httpServletResponse.setStatus(402); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("text/html;charset=UTF-8"); httpServletResponse.getWriter().write(GsonUtil.GSON.toJson(response)); } }
8.自定义权限不足处理器:返回状态码403
@Component public class UrlAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { UrlResponse response = new UrlResponse(); response.setSuccess(false); response.setCode("403"); response.setMessage(e.getMessage()); response.setData(null); httpServletResponse.setStatus(403); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("text/html;charset=UTF-8"); httpServletResponse.getWriter().write(GsonUtil.GSON.toJson(response)); } }
9.自定义注销成功处理器:返回状态码200
@Component public class UrlLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { UrlResponse response = new UrlResponse(); response.setSuccess(true); response.setCode("200"); response.setMessage("Logout Success!!"); response.setData(null); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("text/html;charset=UTF-8"); httpServletResponse.getWriter().write(GsonUtil.GSON.toJson(response)); } }
10.application.yml
server: port: 8018 servlet: session: timeout: 6h spring: # main: # web-application-type: none # security: # user: # name: security_admin #设置默认登录用户名 # password: security_admin #设置默认登录密码 不设置会在控制台打印出,默认用户名是user datasource: url: ######:3306/block?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2b8&autoReconnect=true&failOverReadOnly=false username: ###### password: ###### driver-class-name: com.mysql.jdbc.Driver hikari: read-only: false connection-timeout: 60000 idle-timeout: 60000 validation-timeout: 3000 max-lifetime: 60000 login-timeout: 5 maximum-pool-size: 60 minimum-idle: 10 jpa: generate-ddl: false show-sql: false hibernate: ddl-auto: none database: mysql open-in-view: true
12.数据库表结构设计
SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for authority_user -- ---------------------------- DROP TABLE IF EXISTS `authority_user`; CREATE TABLE `authority_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `username` varchar(255) DEFAULT NULL COMMENT '用户名', `password` varchar(255) DEFAULT NULL COMMENT '密码', `email` varchar(255) DEFAULT NULL COMMENT '邮箱', `phone` varchar(255) DEFAULT NULL COMMENT '手机号', `valid_time` varchar(255) DEFAULT NULL COMMENT '有效截止时间', `update_time` varchar(255) DEFAULT NULL COMMENT '更新时间', `remark` mediumtext COMMENT '备注', `nickname` varchar(255) DEFAULT NULL COMMENT '昵称', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for authority_role -- ---------------------------- DROP TABLE IF EXISTS `authority_role`; CREATE TABLE `authority_role` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `role_name` varchar(255) DEFAULT NULL COMMENT '角色名称(必须以ROLE_起始命名)', `role_name_CN` varchar(255) DEFAULT NULL COMMENT '角色名称中文', `update_time` varchar(255) DEFAULT NULL COMMENT '更新时间', `remark` varchar(255) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for authority_menu -- ---------------------------- DROP TABLE IF EXISTS `authority_menu`; CREATE TABLE `authority_menu` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `url` varchar(255) DEFAULT NULL COMMENT '请求路径', `menu_name` varchar(255) DEFAULT NULL COMMENT '菜单名称', `parent_id` int(11) DEFAULT NULL COMMENT '父菜单id', `update_time` varchar(255) DEFAULT NULL COMMENT '更新时间', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `url_pre` varchar(255) DEFAULT NULL COMMENT '路由(前端自己匹配用)', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=73 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for authority_user_role -- ---------------------------- DROP TABLE IF EXISTS `authority_user_role`; CREATE TABLE `authority_user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `user_id` int(11) DEFAULT NULL, `role_id` int(11) DEFAULT NULL, `update_time` varchar(255) DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for authority_role_menu -- ---------------------------- DROP TABLE IF EXISTS `authority_role_menu`; CREATE TABLE `authority_role_menu` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `role_id` int(11) DEFAULT NULL, `menu_id` int(11) DEFAULT NULL, `update_time` varchar(255) DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=798 DEFAULT CHARSET=utf8;
GitHub: link. 欢迎star
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。