代码拉取完成,页面将自动刷新
说明:
普通用户张三:
数据库管理员李四:
超级管理员王五:
注:上图中,只对相对关键的内容进行了简单说明;完整测试项目,可详见文末链接
。
提示: 完整测试项目,可详见文末链接
。
MyAccessDecisionManager:
import com.pingan.springsecurity.model.MyUserDetails;
import com.pingan.springsecurity.service.impl.MyUserDetailsService;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
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.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* 决策器 (判断鉴权是否通过)
*
* @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
* @date 2019/12/14 11:34
*/
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
/**
* 决策
*
* @param authentication
* 当前用户的信息模型
* 注: 由于重写了{@link MyUserDetailsService#loadUserByUsername},
* 重写后该方法实际返回的类型是{@link MyUserDetails}, 所以这里直接
* 将Authentication强转为MyUserDetails。
* @param object
* 当前request的封装
* @param configAttributes
* 与访问的目标uri相关联的配置属性
* 注:在本示例中,不需要用到此属性; 如果在这里需要用到此属性的话,可以在通过
* 实现{@link FilterInvocationSecurityMetadataSource},重写相关返
* 回Collection<ConfigAttribute>的方法,该返回值会作为形参传递到本方法
* 然后在这里就能拿到对应的值了。
* 可参考网友的示例<linked>https://www.jianshu.com/p/e715cc993bf0</linked>
*
* @throws AccessDeniedException
* 当前用户无权访问
* @throws InsufficientAuthenticationException
* 当前用户信任级别不够,无法访问
* @date 2019/12/14 12:06
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
String targetPath = request.getRequestURI();
Object principal = authentication.getPrincipal();
/*
* 若认证过,那么principal instanceof MyUserDetails为true, 否则用户没有认证过
*
* 注: 鉴权一般都在认证之后, 没有认证谈何鉴权。
*
* 注: 一旦抛出异常后,
* 在{@link ExceptionTranslationFilter#doFilter}中会对抛出的AuthenticationException异常(包括其子异常)
* 进行相关处理。如: 抛出AuthenticationCredentialsNotFoundException异常,就会被处理然后页面跳转至登录页
*
*/
if (!(principal instanceof MyUserDetails)) {
throw new AuthenticationCredentialsNotFoundException(" there is no any Authentication object MyUserDetails in the SecurityContext");
}
MyUserDetails myUserDetails = (MyUserDetails)principal;
// 这个用户可访问的所有资源信息
Collection<? extends GrantedAuthority> grantedAuthority = myUserDetails.getAuthorities();
List<String> list = grantedAuthority.parallelStream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
if(list.contains(targetPath)) {
// 鉴权通过, 有访问权限
return;
}
// 鉴权不通过, 没有访问权限
throw new AccessDeniedException(
String.format("You(%s) don't have any authorizion access %s", myUserDetails.getName(), targetPath)
);
}
/**
* 当前AccessDecisionManager实例能否处理 传递的ConfigAttribute呈现的授权请求
*/
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
/**
* 当前AccessDecisionManager实例是否支持提供访问控制决策
*/
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
MyFilterInvocationSecurityMetadataSource:
import com.pingan.springsecurity.mapper.DaoMapper;
import com.pingan.springsecurity.model.ApiResource;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* 存储ConfigAttribute信息, 并根据ConfigAttribute信息的有无, 决定是否走 决策器
* 即: 若{@link this#getAttributes}返回的集合满足CollectionUtils.isEmpty(list)为true的话,
* 那么不会走决策器,
* 否者会走决策器
*
* @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
* @date 2019/12/14 11:20
*/
@Component
@RequiredArgsConstructor
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private List<String> needAuthPaths = new ArrayList<>(16);
private final DaoMapper mapper;
@PostConstruct
private void init() {
needAuthPaths.addAll(mapper.selectNeedAuthPaths());
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
String targetPath = request.getRequestURI();
if (needAuthPaths.contains(targetPath)) {
// 需要鉴权
List<ConfigAttribute> list = new ArrayList<>(1);
list.add(ApiResource.builder().build());
return list;
}
// 不需要鉴权
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
MySecurityInterceptor:
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
/**
* 通过MySecurityInterceptor 注册 MyAccessDecisionManager
*
* @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
* @date 2019/12/14 12:50
*/
@Component
public class MySecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
private final MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
public MySecurityInterceptor(MyAccessDecisionManager myAccessDecisionManager,
MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource) {
this.myFilterInvocationSecurityMetadataSource = myFilterInvocationSecurityMetadataSource;
// 设置 以 自定义的决策权 进行 鉴权管理
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
/**
* 返回自定义的SecurityMetadataSource
*/
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
// 如果有实现SecurityMetadataSource的话,可以设置采用自定义的 资源器, 如:
// 如果实现有FilterInvocationSecurityMetadataSource的话,可以将其进行注册(即:返回其实例)
return myFilterInvocationSecurityMetadataSource;
}
}
MyWebSecurityConfigurerAdapter:
import com.pingan.springsecurity.service.impl.MyUserDetailsService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* SpringSecurity配置
*
* @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
* @date 2019/12/7 14:08
*/
@Configuration
@RequiredArgsConstructor
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
private final MyUserDetailsService myUserDetailsService;
@Override
public void configure(WebSecurity web) {
/*
* 对于那些没必要进行保护的资源, 可以使用ignoring,使其跳过SpringSecurity
*
* 注:configure(HttpSecurity http)方法里的permitAll();也有类似的效果,
* 不过permitAll会走SpringSecurity,只是说无条件放行而已。
*/
web.ignoring().antMatchers("/picture/**");
web.ignoring().antMatchers("/md/**");
// 开发时,可以将SpringSecurity的debug打开
web.debug(false);
}
/**
* SpringSecurity提供有一些基本的页面(如:login、logout等);如果觉得它提供的
* 基础页面难看,想使用自己的页面的话,可以在此方法里面进行相关配置。
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 设置登录方式为 表单登录
http.formLogin();
/// 设置登录方式为 弹框登录
/// http.httpBasic();
/// 自定义登录页
/// http.formLogin().loginPage("myLoginPae");
/// 自定义登出页
/// http.logout().logoutUrl("myLogoutPae");
// 登出成功时,跳转至此url
http.logout().logoutSuccessUrl("/logout/success");
// 登录成功时,跳转至此url
// 注意:如果未登录,直接访问 登录失败页的话,会被DefaultLoginPageGeneratingFilter识别,并跳转至登录页进行登录
http.formLogin().successForwardUrl("/index");
// 登录失败时,跳转至此url
// 注意:如果未登录,直接访问 登录失败页的话,会被DefaultLoginPageGeneratingFilter识别,并跳转至登录页进行登录
http.formLogin().failureUrl("/login/failed");
/// 当鉴权不通过,是 跳转至此url
http.exceptionHandling().accessDeniedPage("/403");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 配置 UserDetailsService, 用户自定义查询用户的信息
auth.userDetailsService(myUserDetailsService);
}
/**
* 自定义 加密器
*
* 注:只需要将其注册进入容器中即可,InitializeUserDetailsBeanManagerConfigurer类会从容器
* 拿去PasswordEncoder.class实现,作为其加密器
*
* @date 2019/12/21 17:59
*/
@Bean
public PasswordEncoder myPasswordEncoder() {
return new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
return rawPassword == null ? "" : rawPassword.toString();
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null || rawPassword.length() == 0) {
return false;
}
return rawPassword.equals(encodedPassword);
}
};
}
}
MyUserDetailsService:
import com.pingan.springsecurity.mapper.DaoMapper;
import com.pingan.springsecurity.model.ApiResource;
import com.pingan.springsecurity.model.MyUserDetails;
import com.pingan.springsecurity.model.Role;
import lombok.RequiredArgsConstructor;
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 java.util.List;
import java.util.stream.Collectors;
/**
* 对{@link UserDetailsService#loadUserByUsername(String)}进行重写
*
* @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
* @date 2019/12/14 10:20
*/
@Service
@RequiredArgsConstructor
public class MyUserDetailsService implements UserDetailsService {
private final DaoMapper mapper;
/**
* 根据账号名, 查询用户信息
*
* todo 用户名不存在时,不处理的话,最终抛出InternalAuthenticationServiceException
*/
@Override
public UserDetails loadUserByUsername(String accountNo) throws UsernameNotFoundException {
// 查询用户基本信息
MyUserDetails myUserDetails = mapper.selectUserBasicInfoByAccountNo(accountNo);
// 查询用户角色信息
List<Role> roleList = mapper.selectRolesByUserId(myUserDetails.getId());
// 查询用户权限信息(即:查询用户可访问的资源)
List<Integer> roleIdList = roleList.parallelStream().map(Role::getId).collect(Collectors.toList());
List<ApiResource> apiResources = mapper.selectApiResourcesByRoleIds(roleIdList);
// 组装信息并返回
myUserDetails.setRoles(roleList);
myUserDetails.setAccessibleApis(apiResources);
return myUserDetails;
}
}
这里只作简单提示不展开
通过SpEL指定调用自定义类实现自定义鉴权。即:在继承WebSecurityConfigurerAdapter作相关配置时,指定access调用自定义的方法即可
@Override
protected void configure(HttpSecurity http) throws Exception {
// 通过SPEL调用自定义的spring bean的方法即可, 该spring bean中应该有对应的方法,如:public boolean hasPermission(HttpServletRequest request, Authentication authentication)
http.authorizeRequests()
.antMatchers("/**").access("@mySpringBean.hasPermission(request, authentication)");
}
Spring Security账号密码认证 + 自定义鉴权,简单示例完毕 !
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。