SpringBoot2动态权限校验
("hasAuthority('ROLE_TELLER')")public Account post(Account account, double amount);
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><version>2.3.4.RELEASE</version></dependency><!-- 默认通过SESSIONId改为通过请求头与redis配合验证session --><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId><version>2.3.1.RELEASE</version></dependency><!--redis支持--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.3.4.RELEASE</version></dependency>
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** Swagger等静态资源不进行拦截*/public void configure(WebSecurity web) {web.ignoring().antMatchers("/*.html","/favicon.ico","/**/*.html","/**/*.css","/**/*.js","/error","/webjars/**","/resources/**","/swagger-ui.html","/swagger-resources/**","/v2/api-docs");}protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//配置一些不需要登录就可以访问的接口.antMatchers("/demo/**", "/about/**").permitAll()//任何尚未匹配的URL只需要用户进行身份验证.anyRequest().authenticated().and().formLogin()//允许用户进行基于表单的认证.loginPage("/mylogin");}}

public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationSuccessHandler.class);public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.toString());//登录成功返回的认证体,具体格式在后面的登录认证管理器中String responseJson = JackJsonUtil.object2String(ResponseFactory.success(authentication));if (LOGGER.isDebugEnabled()) {LOGGER.debug("登录成功!");}response.getWriter().write(responseJson);}}
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationFailureHandler.class);public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {String errorMsg;if (StringUtils.isNotBlank(e.getMessage())) {errorMsg = e.getMessage();} else {errorMsg = CodeMsgEnum.LOG_IN_FAIL.getMsg();}response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.toString());String responseJson = JackJsonUtil.object2String(ResponseFactory.fail(CodeMsgEnum.LOG_IN_FAIL,errorMsg));if (LOGGER.isDebugEnabled()) {LOGGER.debug("认证失败!");}response.getWriter().write(responseJson);}}
public class UserVerifyAuthenticationProvider implements AuthenticationProvider {private PasswordEncoder passwordEncoder;private UserService userService;public Authentication authenticate(Authentication authentication) throws AuthenticationException {String userName = (String) authentication.getPrincipal(); // Principal 主体,一般指用户名String passWord = (String) authentication.getCredentials(); //Credentials 网络凭证,一般指密码//通过账号去数据库查询用户以及用户拥有的角色信息UserRoleVo userRoleVo = userService.findUserRoleByAccount(userName);//数据库密码String encodedPassword = userRoleVo.getPassWord();//credentials凭证即为前端传入密码,因为前端一般用Base64加密过所以需要解密。String credPassword = new String(Base64Utils.decodeFromString(passWord), StandardCharsets.UTF_8);// 验证密码:前端明文,数据库密文passwordEncoder = new MD5Util();if (!passwordEncoder.matches(credPassword, encodedPassword)) {throw new AuthenticationServiceException("账号或密码错误!");}//ps:GrantedAuthority对认证主题的应用层面的授权,含当前用户的权限信息,通常使用角色表示List<GrantedAuthority> roles = new LinkedList<>();List<Role> roleList = userRoleVo.getRoleList();roleList.forEach(role -> {SimpleGrantedAuthority roleId = new SimpleGrantedAuthority(role.getRoleId().toString());roles.add(roleId);});UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, passWord, roles);token.setDetails(userRoleVo);//这里可以放用户的详细信息return token;}public boolean supports(Class<?> authentication) {return false;}}
public class LoginFilter extends UsernamePasswordAuthenticationFilter {private UserVerifyAuthenticationProvider authenticationManager;/*** @param authenticationManager 认证管理器* @param successHandler 认证成功处理类* @param failureHandler 认证失败处理类*/public LoginFilter(UserVerifyAuthenticationProvider authenticationManager,CustomAuthenticationSuccessHandler successHandler,CustomAuthenticationFailureHandler failureHandler) {//设置认证管理器(对登录请求进行认证和授权)this.authenticationManager = authenticationManager;//设置认证成功后的处理类this.setAuthenticationSuccessHandler(successHandler);//设置认证失败后的处理类this.setAuthenticationFailureHandler(failureHandler);//可以自定义登录请求的urlsuper.setFilterProcessesUrl("/myLogin");}public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {try {//转换请求入参UserDTO loginUser = new ObjectMapper().readValue(request.getInputStream(), UserDTO.class);//入参传入认证管理器进行认证return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginUser.getUserName(), loginUser.getPassWord()));} catch (IOException e) {e.printStackTrace();return null;}}}
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {private UserVerifyAuthenticationProvider authenticationManager;//认证用户类private CustomAuthenticationSuccessHandler successHandler;//登录认证成功处理类private CustomAuthenticationFailureHandler failureHandler;//登录认证失败处理类/*** Swagger等静态资源不进行拦截*/public void configure(WebSecurity web) {web.ignoring().antMatchers("/*.html","/favicon.ico","/**/*.html","/**/*.css","/**/*.js","/error","/webjars/**","/resources/**","/swagger-ui.html","/swagger-resources/**","/v2/api-docs");}protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//配置一些不需要登录就可以访问的接口.antMatchers("/demo/**", "/about/**").permitAll()//任何尚未匹配的URL只需要用户进行身份验证.anyRequest().authenticated().and()//配置登录过滤器.addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler)).csrf().disable();}}



session:: redisredis:namespace: spring:session:admin# session 无操作失效时间 30 分钟timeout: 1800
/*** 配置 HttpSessionIdResolver Bean* 登录之后将会在 Response Header x-auth-token 中 返回当前 sessionToken* 将token存储在前端 每次调用的时候 Request Header x-auth-token 带上 sessionToken*/@Beanpublic HttpSessionIdResolver httpSessionIdResolver() {return HeaderHttpSessionIdResolver.xAuthToken();}

protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//配置一些不需要登录就可以访问的接口.antMatchers("/demo/**", "/about/**").permitAll()//任何尚未匹配的URL只需要用户进行身份验证.anyRequest().authenticated().and()//配置登录过滤器.addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler)).csrf().disable();//配置头部http.headers().contentTypeOptions().and().xssProtection().and()//禁用缓存.cacheControl().and().httpStrictTransportSecurity().and()//禁用页面镶嵌frame劫持安全协议 // 防止iframe 造成跨域.frameOptions().disable();}



package com.aliyu.security.provider;/*** @author: aliyu* @create: 2021-02-05 14:53* @description:*/import com.aliyu.service.role.RoleService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.access.ConfigAttribute;import org.springframework.security.access.SecurityConfig;import org.springframework.security.web.FilterInvocation;import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;import java.util.Collection;import java.util.List;import java.util.Map;/***@author: aliyu*@create:*@description: 第一步:数据库查询所有权限出来:* 之所以要所有权限,因为数据库url和实际请求url并不能直接匹配需要。比方:/user/getUserById 匹配 /user/getUserById?1* 第二步:通过httpUrl匹配器找出允许访问当前请求的角色列表(哪些角色可以访问此请求)*/public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {private RoleService roleService;/*** 返回当前URL允许访问的角色列表* @param object* @return* @throws IllegalArgumentException*/public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {//入参转为HttpServletRequestFilterInvocation fi = (FilterInvocation) object;HttpServletRequest request = fi.getRequest();//从数据库中查询系统所有的权限,格式为<"权限url","能访问url的逗号分隔的roleid">List<Map<String, String>> allUrlRoleMap = roleService.getAllUrlRoleMap();for (Map<String, String> urlRoleMap : allUrlRoleMap) {String url = urlRoleMap.get("url");String roles = urlRoleMap.get("roles");//new AntPathRequestMatcher创建httpUrl匹配器:里面url匹配规则已经给我们弄好了,// 能够支持校验PathVariable传参的url(例如:/getUserByName/{name})// 也能支持 /user/getUserById 匹配 /user/getUserById?1AntPathRequestMatcher matcher = new AntPathRequestMatcher(url);if (matcher.matches(request)){ //当前请求与httpUrl匹配器进行匹配return SecurityConfig.createList(roles.split(","));}}return null;}public Collection<ConfigAttribute> getAllConfigAttributes() {return null;}public boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}}
package com.aliyu.security.provider;/*** @author: aliyu* @create: 2021-02-05 15:16* @description:*/import org.apache.commons.lang3.StringUtils;import org.springframework.security.access.AccessDecisionManager;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.access.ConfigAttribute;import org.springframework.security.authentication.AnonymousAuthenticationToken;import org.springframework.security.authentication.InsufficientAuthenticationException;import org.springframework.security.core.Authentication;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.web.FilterInvocation;import org.springframework.stereotype.Component;import java.util.Collection;import java.util.Iterator;/***@author: aliyu*@create:*@description: 接口权限判断(根据MyFilterInvocationSecurityMetadataSource获取到的请求需要的角色* 和当前登录人的角色进行比较)*/public class MyAccessDecisionManager implements AccessDecisionManager {public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {//循环请求需要的角色,只要当前用户拥有的角色中包含请求需要的角色中的一个,就算通过。Iterator<ConfigAttribute> iterator = configAttributes.iterator();while(iterator.hasNext()){ConfigAttribute configAttribute = iterator.next();String needCode = configAttribute.getAttribute();//获取到了登录用户的所有角色Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();for (GrantedAuthority authority : authorities) {if (StringUtils.equals(authority.getAuthority(), needCode)) {return;}}}throw new AccessDeniedException("当前访问没有权限");}public boolean supports(ConfigAttribute attribute) {return false;}public boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}}
package com.aliyu.security.handler;import com.aliyu.common.util.JackJsonUtil;import com.aliyu.entity.common.vo.ResponseFactory;import com.aliyu.security.constant.MessageConstant;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.http.MediaType;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.nio.charset.StandardCharsets;import static com.aliyu.entity.common.exception.CodeMsgEnum.MOVED_PERMANENTLY;/*** 未登录重定向处理器* <p>* 未登录状态下访问需要登录的接口** @author*/public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.toString());//原来不需要登录的接口,现在需要登录了,所以叫永久移动String message = JackJsonUtil.object2String(ResponseFactory.fail(MOVED_PERMANENTLY, MessageConstant.NOT_LOGGED_IN));if (LOGGER.isDebugEnabled()) {LOGGER.debug("未登录重定向!");}response.getWriter().write(message);}}
package com.aliyu.security.handler;import com.aliyu.common.util.JackJsonUtil;import com.aliyu.entity.common.exception.CodeMsgEnum;import com.aliyu.entity.common.vo.ResponseFactory;import com.aliyu.security.constant.MessageConstant;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.http.MediaType;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.web.access.AccessDeniedHandler;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.nio.charset.StandardCharsets;/*** 拒绝访问处理器(登录状态下,访问没有权限的方法时会进入此处理器)** @author*/public class CustomAccessDeniedHandler implements AccessDeniedHandler {private static final Logger LOGGER = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException {response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.toString());String message = JackJsonUtil.object2String(ResponseFactory.fail(CodeMsgEnum.UNAUTHORIZED, MessageConstant.NO_ACCESS));if(LOGGER.isDebugEnabled()){LOGGER.debug("没有权限访问!");}response.getWriter().write(message);}}
private MyFilterInvocationSecurityMetadataSource securityMetadataSource;//返回当前URL允许访问的角色列表private MyAccessDecisionManager accessDecisionManager;//除登录登出外所有接口的权限校验
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O object) {object.setAccessDecisionManager(accessDecisionManager);object.setSecurityMetadataSource(securityMetadataSource);return object;}})
//用来解决匿名用户访问无权限资源时的异常.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())//用来解决登陆认证过的用户访问无权限资源时的异常.accessDeniedHandler(new CustomAccessDeniedHandler())
package com.aliyu.security.config;import com.aliyu.filter.LoginFilter;import com.aliyu.security.handler.*;import com.aliyu.security.provider.MyAccessDecisionManager;import com.aliyu.security.provider.MyFilterInvocationSecurityMetadataSource;import com.aliyu.security.provider.UserVerifyAuthenticationProvider;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.ObjectPostProcessor;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.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;import org.springframework.session.web.http.HeaderHttpSessionIdResolver;import org.springframework.session.web.http.HttpSessionIdResolver;public class WebSecurityConfig extends WebSecurityConfigurerAdapter {private UserVerifyAuthenticationProvider authenticationManager;//认证用户类private CustomAuthenticationSuccessHandler successHandler;//登录认证成功处理类private CustomAuthenticationFailureHandler failureHandler;//登录认证失败处理类private MyFilterInvocationSecurityMetadataSource securityMetadataSource;//返回当前URL允许访问的角色列表private MyAccessDecisionManager accessDecisionManager;//除登录登出外所有接口的权限校验/*** 密码加密* @return*/(PasswordEncoder.class)public PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 配置 HttpSessionIdResolver Bean* 登录之后将会在 Response Header x-auth-token 中 返回当前 sessionToken* 将token存储在前端 每次调用的时候 Request Header x-auth-token 带上 sessionToken*/public HttpSessionIdResolver httpSessionIdResolver() {return HeaderHttpSessionIdResolver.xAuthToken();}/*** Swagger等静态资源不进行拦截*/public void configure(WebSecurity web) {web.ignoring().antMatchers("/*.html","/favicon.ico","/**/*.html","/**/*.css","/**/*.js","/error","/webjars/**","/resources/**","/swagger-ui.html","/swagger-resources/**","/v2/api-docs");}protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//配置一些不需要登录就可以访问的接口.antMatchers("/demo/**", "/about/**").permitAll()//任何尚未匹配的URL只需要用户进行身份验证.anyRequest().authenticated()//登录后的接口权限校验.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {public <O extends FilterSecurityInterceptor> O postProcess(O object) {object.setAccessDecisionManager(accessDecisionManager);object.setSecurityMetadataSource(securityMetadataSource);return object;}}).and()//配置登出处理.logout().logoutUrl("/logout").logoutSuccessHandler(new CustomLogoutSuccessHandler()).clearAuthentication(true).and()//用来解决匿名用户访问无权限资源时的异常.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())//用来解决登陆认证过的用户访问无权限资源时的异常.accessDeniedHandler(new CustomAccessDeniedHandler()).and()//配置登录过滤器.addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler)).csrf().disable();//配置头部http.headers().contentTypeOptions().and().xssProtection().and()//禁用缓存.cacheControl().and().httpStrictTransportSecurity().and()//禁用页面镶嵌frame劫持安全协议 // 防止iframe 造成跨域.frameOptions().disable();}}


微信赞赏
支付宝扫码领红包
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。侵权投诉:375170667@qq.com









