标签:影响 edr equal replace fail localhost mapping upper param
要实现权限控制首先得把数据库设计好。
我这边是这么设计的有一些字段可能比较多。仅供参考。
我们之前做了一些基础的配置,现在在这个基础的配置上进行改造。一步步打造成我们需要的功能。
AdminAuthenticationSuccessHandler
@Component
public class AdminAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// SecurityUser 是Spring Security里面UserDetailsd的实现类里面包含了用户信息
SecurityUser securityUser = ((SecurityUser) authentication.getPrincipal());
// 从SecurityUser 中取出Token
String token = securityUser.getToken();
// 定义一个map集合 用于返回JSON数据
HashMap<String, String> map = new HashMap<String, String>(1);
// 前端会收到 {"token":"token值"}
map.put("token",token);
// 结果集封装 规范返回内容
Object result = ApiResult.ok(map);
// 将封装数据进行返回。 这里有一个返回工具类
ResponseUtils.renderJson(httpServletResponse, JacksonUtil.toJson(result));
}
}
上面提到了 SecurityUser 类 ,这里贴一下我的代码
SecurityUser
// 注解 避免JSON返回不必要的空数据
@JsonIgnoreProperties(ignoreUnknown = true)
public class SecurityUser implements UserDetails {
/**
* 当前登录用户
*/
private User currentUserInfo;
/**
* 角色
*/
private List<Role> roleList;
/**
* 用户权限值
*/
private List<Permission> permissionList;
private String token;
/**
* 空参构造
*/
public SecurityUser() {
}
/**
* 用户信息构造
*
* @param user
*/
public SecurityUser(User user) {
if (user != null) {
this.currentUserInfo = user;
}
}
public SecurityUser(User user, List<Role> roleList) {
if (user != null) {
this.currentUserInfo = user;
this.roleList = roleList;
}
}
public SecurityUser(User user, List<Role> roleList, List<Permission> permissionList) {
if (user != null) {
this.currentUserInfo = user;
this.roleList = roleList;
this.permissionList = permissionList;
}
}
@Override
public String getPassword() {
return currentUserInfo.getPassword();
}
@Override
public String getUsername() {
return currentUserInfo.getUsername();
}
/**
* 用户是否过期
*
* @return
*/
@Override
public boolean isAccountNonExpired() {
return false;
}
/**
* 用户是否被锁定
*
* @return true 用户被锁定 false用户没有被锁定
*/
@Override
public boolean isAccountNonLocked() {
return false;
}
/**
* 账户登录凭证是否过期
*
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return false;
}
/**
* 用户是否被禁用
*
* @return true是 false否
*/
@Override
public boolean isEnabled() {
return false;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public User getCurrentUserInfo() {
return currentUserInfo;
}
public List<Role> getRoleList() {
return roleList;
}
public List<Permission> getPermissionList() {
return permissionList;
}
/**
* 获取当前用户具有的角色
* 用户添加权限。返回权限对象
* 这个方法很重要,会影响到后面的权限资源控制
* @return GrantedAuthority
*/
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new HashSet<>();
// 判断权限列表是否为空
if (!CollectionUtils.isEmpty(this.permissionList)) {
// 对权限列表进行遍历
for (Permission permission : this.permissionList) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission.getAccessCode());
// 将权限添加到集合中
authorities.add(authority);
}
}
//返回权限
return authorities;
}
}
AdminAuthenticationFailureHandler
@Slf4j
@Component
public class AdminAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
Object result;
// 对异常信息进行封装处理返回给前端
if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
result = ApiResult.fail(HttpStatus.PAYMENT_REQUIRED.code(),e.getMessage());
} else if (e instanceof LockedException) {
result = ApiResult.fail(HttpStatus.PAYMENT_REQUIRED.code(),"账户被锁定,请联系管理员!");
} else if (e instanceof CredentialsExpiredException) {
result = ApiResult.fail(HttpStatus.PAYMENT_REQUIRED.code(),"证书过期,请联系管理员!");
} else if (e instanceof AccountExpiredException) {
result = ApiResult.fail(HttpStatus.PAYMENT_REQUIRED.code(),"账户过期,请联系管理员!");
} else if (e instanceof DisabledException) {
result = ApiResult.fail(HttpStatus.PAYMENT_REQUIRED.code(),"账户被禁用,请联系管理员!");
} else {
log.error("登录失败:", e);
result = ApiResult.fail(HttpStatus.PAYMENT_REQUIRED.code(),"登录失败!");
}
ResponseUtils.renderJson(response, JacksonUtil.toJson(result));
}
}
AdminLogoutSuccessHandler
@Component
public class AdminLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
Object result = ApiResult.ok("退出成功");
ResponseUtils.renderJson(httpServletResponse, result);
}
}
SecurityConfig
@EnableWebSecurity
// 开启注解权限资源控制
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 自定义认证处理器 (重点:额外添加的类,用于获取用户信息,保存用户信息。并且处理登录等操作)
*/
private final AdminAuthenticationProvider adminAuthenticationProvider;
/**
* 登录成功处理器
*/
private final AdminAuthenticationSuccessHandler adminAuthenticationSuccessHandler;
/**
* 登录失败处理器
*/
private final AdminAuthenticationFailureHandler adminAuthenticationFailureHandler;
/**
* 未登录情况下的处理类 (额外添加的类 这个类在下方有说明)
*/
private final AdminAuthenticationEntryPoint adminAuthenticationEntryPoint;
/**
* Token 处理器 (额外添加的类 这个类在下方有说明)
*/
private final MyTokenAuthenticationFilter myTokenAuthenticationFilter;
/**
* 退出登录处理类 (我在这里处理了一下缓存的token退出的时候清除掉了,这个类下方也有说明)
*/
private final AdminLogoutHandler adminLogoutHandler;
/**
* 退出成功处理
*/
private final AdminLogoutSuccessHandler adminLogoutSuccessHandler;
// 上面是登录认证相关 下面为url权限相关 - ========================================================================================
/**
* 登陆过后无权访问返回处理类 (返回无权的时候的返回信息处理)
*/
private final MyAccessDeniedHandler myAccessDeniedHandler;
public SecurityConfig(AdminAuthenticationProvider adminAuthenticationProvider, AdminAuthenticationSuccessHandler adminAuthenticationSuccessHandler, AdminAuthenticationFailureHandler adminAuthenticationFailureHandler, AdminAuthenticationEntryPoint adminAuthenticationEntryPoint, MyTokenAuthenticationFilter myTokenAuthenticationFilter, AdminLogoutHandler adminLogoutHandler, AdminLogoutSuccessHandler adminLogoutSuccessHandler, MyAccessDeniedHandler myAccessDeniedHandler) {
this.myTokenAuthenticationFilter = myTokenAuthenticationFilter;
this.adminAuthenticationProvider = adminAuthenticationProvider;
this.adminAuthenticationSuccessHandler = adminAuthenticationSuccessHandler;
this.adminAuthenticationFailureHandler = adminAuthenticationFailureHandler;
this.adminAuthenticationEntryPoint = adminAuthenticationEntryPoint;
this.adminLogoutHandler = adminLogoutHandler;
this.adminLogoutSuccessHandler = adminLogoutSuccessHandler;
this.myAccessDeniedHandler = myAccessDeniedHandler;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.antMatcher("/**").authorizeRequests();
// 标记只能在 服务器本地ip[127.0.0.1或者localhost] 访问`/home`接口,其他ip地址无法访问
// registry.antMatchers("/").hasIpAddress("127.0.0.1");
// 允许匿名的url - 可理解为放行接口 - 多个接口使用,分割
registry.antMatchers("/home").permitAll();
// OPTIONS(选项):查找适用于一个特定网址资源的通讯选择。 在不需执行具体的涉及数据传输的动作情况下, 允许客户端来确定与资源相关的选项以及 / 或者要求, 或是一个服务器的性能
// registry.antMatchers(HttpMethod.OPTIONS, Constants.CONTEXT_PATH+"/**").denyAll();
// 其余所有请求都需要认证
registry.anyRequest().authenticated();
// 禁用CSRF 开启跨域
http.csrf().disable();
// 开启CSRF 向前端发送 XSRF-TOKEN Cookie(上面设置了关闭,可以通过下述方式进行开启,但是swagger-ui的跨域403异常暂时找不到解决办法。无奈放弃了)
// http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
//配置HTTP基本身份认证
http.httpBasic();
// 未登录认证异常
http.exceptionHandling().authenticationEntryPoint(adminAuthenticationEntryPoint);
// 登陆过后无权访问返回
http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
// 登录处理 - 前后端一体的情况下
http.formLogin().loginProcessingUrl("/user/login")
// 默认的登录成功返回url
// .defaultSuccessUrl("/")
// 登录成功处理
.successHandler(adminAuthenticationSuccessHandler)
.failureHandler(adminAuthenticationFailureHandler)
// 自定义登陆用户名和密码属性名,默认为 username和password
.usernameParameter("username").passwordParameter("password")
// 异常处理
// .failureUrl(Constants.CONTEXT_PATH+"/login/error").permitAll()
// 退出登录
.and().logout().logoutUrl("/user/logout").permitAll()
.addLogoutHandler(adminLogoutHandler)
.logoutSuccessHandler(adminLogoutSuccessHandler);
// session创建规则 STATELESS 不使用session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 防止iframe 造成跨域
http.headers().frameOptions().disable();
// 添加前置的过滤器 用于验证token
http.addFilterBefore(myTokenAuthenticationFilter, BasicAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 自定义验证管理器
auth.authenticationProvider(adminAuthenticationProvider);
// super.configure(auth);
}
@Override
public void configure(WebSecurity web) throws Exception {
// ignoring 允许添加 RequestMatcher Spring Security 应该忽略的实例。
web.ignoring().antMatchers(HttpMethod.GET,
"/favicon.ico",
"/*.html",
"/**/*.css",
"/**/*.js");
web.ignoring().antMatchers(HttpMethod.GET,"/swagger-resources/**");
web.ignoring().antMatchers(HttpMethod.GET,"/webjars/**");
web.ignoring().antMatchers(HttpMethod.GET,"/v2/api-docs");
web.ignoring().antMatchers(HttpMethod.GET,"/v2/api-docs-ext");
web.ignoring().antMatchers(HttpMethod.GET,"/configuration/ui");
web.ignoring().antMatchers(HttpMethod.GET,"/configuration/security");
}
}
用于从获取用户信息、保存用户信息,并且处理登录等操作。
AdminAuthenticationProvider
@Component
public class AdminAuthenticationProvider implements AuthenticationProvider {
private final
UserDetailsServiceImpl userDetailsService;
private final RedisUtil redisUtil;
@Autowired
public AdminAuthenticationProvider(UserDetailsServiceImpl userDetailsService, RedisUtil redisUtil) {
this.userDetailsService = userDetailsService;
this.redisUtil = redisUtil;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取前端表单中输入后返回的用户名、密码
String userName = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
// 通过用户名称获取用户信息 (UserDetailsServiceImpl 这个类是实现了 Security 中提供的 UserDetailsService 用于处理用户信息 具体内容在下方)
SecurityUser userInfo = (SecurityUser) userDetailsService.loadUserByUsername(userName);
// 判断用户是否存在
if(userInfo == null){
throw new UsernameNotFoundException("当前用户名不存在");
}
// 对可能出现的异常进行抛出,抛出的一次会跳到登录失败中进行集中处理
if(userInfo.isEnabled()){
throw new DisabledException("该账户已被禁用,请联系管理员");
}else if(userInfo.isAccountNonLocked()){
throw new LockedException("该账户已被锁定");
}else if(userInfo.isAccountNonExpired()){
throw new AccountExpiredException("该账户已过期,请联系管理员");
}else if(userInfo.isCredentialsNonExpired()){
throw new CredentialsExpiredException("该账户的登陆凭证已过期,请重新登录");
}
// 密码验证,用于验证传进来的密码和加密后的密码是否是同一个。
boolean isValid = PasswordUtils.checkpw(password, userInfo.getPassword());
if (!isValid) {
//抛出密码异常
throw new BadCredentialsException("密码错误!");
}
// 前后端分离情况下 处理逻辑...
// 更新登录令牌 - 之后访问系统其它接口直接通过token认证用户权限...
String token = PasswordUtils.hashpw(System.currentTimeMillis() + userInfo.getUsername(), PasswordUtils.getSalt());
userInfo.setToken(token);
// 通过 redis 缓存数据
redisUtil.set(token,userInfo,30*60L);
return new UsernamePasswordAuthenticationToken(userInfo,password,userInfo.getAuthorities());
}
@Override
public boolean supports(Class<?> aClass) {
//确保 aClass 能转成该类
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}
上面有提到所以贴出来方便理解整个过程,这个类用于处理用户数据,获取用户信息。
UserDetailsServiceImpl
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserService userService;
private final UserRoleService userRoleService;
private final RoleService roleService;
private final RolePermissionService rolePermissionService;
private final PermissionService permissionService;
@Autowired(required = false)
public UserDetailsServiceImpl(UserMapper userMapper, RoleMapper roleMapper, PermissionMapper permissionMapper, UserRoleMapper userRoleMapper, RolePermissionMapper rolePermissionMapper, GroupPermissionMapper groupPermissionMapper, UserService userService, UserRoleService userRoleService, RoleService roleService, RolePermissionService rolePermissionService, PermissionService permissionService) {
this.userService = userService;
this.userRoleService = userRoleService;
this.roleService = roleService;
this.rolePermissionService = rolePermissionService;
this.permissionService = permissionService;
}
/**
* 根据用户获取用户信息、角色信息、权限信息
*
* @param username:
* @return UserDetails
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库中取出用户信息
User user = userService.queryByUserName(username);
// 判断用户是否存在
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
List<Role> userRoles = getUserRoles(user.getId());
List<Permission> userRolePermissions = getUserRolePermissions(userRoles);
// 返回 SecurityUser 这个类上面有介绍
return new SecurityUser(user,userRoles,userRolePermissions);
}
/**
* 根据用户ID获取角色权限信息
*
* @param id
* @return
*/
private List<Role> getUserRoles(Integer id) {
//通过用户ID查询角色中间表
List<UserRole> userRoles = userRoleService.queryAll(id);
//通过角色中间表查询角色列表
List<Role> roles = roleService.queryByUserRoles(userRoles);
// 返回角色列表
return roles;
}
/**
* 通过角色列表获取权限信息字符串
*
* @return
*/
private Set<String> getUserRolePermissionsAccessCode(List<Role> roleList) {
/**
* 通过角色权限中间表查询
*/
List<RolePermission> rolePermissions = rolePermissionService.queryByRoleList(roleList);
return permissionService.queryByPermissionIdsOrAccessCode(rolePermissions);
}
/**
* 通过角色列表获取权限信息
*
* @return
*/
private List<Permission> getUserRolePermissions(List<Role> roleList) {
/**
* 通过角色权限中间表查询
*/
List<RolePermission> rolePermissions = rolePermissionService.queryByRoleList(roleList);
return permissionService.queryByPermissionIds(rolePermissions);
}
}
MyTokenAuthenticationFilter
@Slf4j
@Component
public class MyTokenAuthenticationFilter extends OncePerRequestFilter {
private final RedisUtil redisUtil;
public MyTokenAuthenticationFilter(RedisUtil redisUtil) {
this.redisUtil = redisUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
log.info("请求头类型: {}" , httpServletRequest.getContentType());
MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(httpServletRequest);
MultiReadHttpServletResponse wrappedResponse = new MultiReadHttpServletResponse(httpServletResponse);
StopWatch stopWatch = new StopWatch();
try {
stopWatch.start();
// 记录请求的消息体
logRequestBody(wrappedRequest);
// 前后端分离情况下,前端登录后将token储存在cookie中,每次访问接口时通过token去拿用户权限
// Constants 这个类是自己定义的常量类REQUEST_HEADER这个表示token头的名称
String token = wrappedRequest.getHeader(Constants.REQUEST_HEADER);
log.debug("后台检查令牌:{}", token);
if (StringUtils.isNotBlank(token)) {
// 检查token
SecurityUser securityUser = (SecurityUser) redisUtil.get(token);
if (securityUser == null || securityUser.getCurrentUserInfo() == null) {
throw new AccessDeniedException("TOKEN已过期,请重新登录!");
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
// 全局注入角色权限信息和登录用户基本信息
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(wrappedRequest, wrappedResponse);
} finally {
stopWatch.stop();
long usedTimes = stopWatch.getTotalTimeMillis();
// 记录响应的消息体
logResponseBody(wrappedRequest, wrappedResponse, usedTimes);
}
}
// 处理请求
private void logRequestBody(MultiReadHttpServletRequest request) {
MultiReadHttpServletRequest wrapper = request;
if (wrapper != null) {
try {
String bodyJson = wrapper.getBodyJsonStrByJson(request);
String url = wrapper.getRequestURI().replace("//", "/");
log.info("-------------------------------- 请求url: " + url + " --------------------------------");
Constants.URL_MAPPING_MAP.put(url, url);
log.info("`{}` 接收到的参数: {}",url , bodyJson);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 处理返回
private void logResponseBody(MultiReadHttpServletRequest request, MultiReadHttpServletResponse response, long useTime) {
MultiReadHttpServletResponse wrapper = response;
if (wrapper != null) {
byte[] buf = wrapper.getBody();
if (buf.length > 0) {
String payload;
try {
payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
} catch (UnsupportedEncodingException ex) {
payload = "[unknown]";
}
log.info("`{}` 耗时:{}ms 返回的参数: {}", Constants.URL_MAPPING_MAP.get(request.getRequestURI()), useTime, payload);
log.info("");
}
}
}
}
到这里其实已经基本上差不多了。
AdminAuthenticationEntryPoint.java
@Slf4j
@Component
public class AdminAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.error(e.getMessage());
// 其中HttpStatus类是自己定义的一个枚举类。用于定义状态码和消息
ResponseUtils.renderJson(httpServletResponse, ApiResult.fail(HttpStatus.UNAUTHORIZED.code(),HttpStatus.UNAUTHORIZED.reasonPhraseCN()));
}
}
MyAccessDeniedHandler.java
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
ResponseUtils.renderJson(httpServletResponse, ApiResult.fail(403, e.getMessage()));
}
}
到这里配置项已经完成了。
这里我简单写了一下 UserController.java
@RestController
@RequestMapping("/api/user")
@Api(value = "用户相关操作",tags="用户相关操作")
public class UserController {
private final
UserMapper userMapper;
@Autowired(required = false)
public UserController(UserMapper userMapper) {
this.userMapper = userMapper;
}
// 需要注意这里的权限控制值,需要与数据库中获取到的值一致
@PreAuthorize("hasAuthority(‘user‘)")
@PostMapping("/userInfo")
@ApiOperation(value = "查询用户信息",notes = "查询用户信息请求头里面需要携带X-Token")
UserInfo getUserInfo() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserInfo userInfo = null;
if(authentication != null){
SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
userInfo = new UserInfo();
userInfo.setUser(securityUser.getCurrentUserInfo());
userInfo.setRoles(securityUser.getRoleList());
userInfo.setPermissions(securityUser.getPermissionList());
userInfo.setPermissionAccessList(securityUser.getPermissionAccessCode());
}
return userInfo;
}
@PreAuthorize("hasAuthority(‘user_add‘)")
@PostMapping("/addUser")
@ApiOperation(value = "添加用户",notes = "添加用户信息")
@ApiImplicitParam()
Object addUser(@RequestBody User user){
user.setPassword(PasswordUtils.hashpw(user.getPassword(),PasswordUtils.getSalt()));
userMapper.insertSelective(user);
return ApiResult.ok("操作成功");
}
@PreAuthorize("hasAuthority(‘no_access‘)")
@PostMapping("/noAccess")
@ApiImplicitParam()
Object noAccess(){
return ApiResult.ok("无权访问的接口");
}
}
需要注意: @PreAuthorize("hasAuthority(‘user‘)")
? 该注解里面的值需要与数据库里面的值保持一致
写了这么多,也要对实际结果进行确认才行。我这里用的 Postman 测试一下。
通过登录接口可以拿到token,token值需要放到请求头中。用于认证用户是否登录。
获取用户信息(注意这里设置了 token 请求头)
访问无权接口
Spring Security(2)基于动态角色资源权限校验
标签:影响 edr equal replace fail localhost mapping upper param
原文地址:https://www.cnblogs.com/kezp/p/13245282.html