本案例是以最简单最简单的方式实现动态权限配置,摒弃各种花里胡哨的代码。
动态权限主要需要实现两个功能:
1、Url访问权限的动态设置
2、用户本身具备的权限动态设置
基础逻辑主要就是用Security作为登录、权限校验,权限允许则访问,权限不允许则提示权限不足。
一、准备工作 1、一个简单的SpringBoot工程略
2、引入3、数据源org.springframework.boot spring-boot-starter-securityorg.springframework.security spring-security-testtest
正常需要三张数据表,用户表、权限表、Url表,然后你就需要读取数据库、连Mybatis、JDBC、Redis巴拉巴拉的,这个跟主题无关,此处用三个写死的类替代就好。
(1)、权限管理类,枚举出来本系统的全部权限
@Service
public class RoseService {
public final String ADMIN = "ADMIN";
public final String USER = "USER";
}
(2)、Url管理类,指定那些Url需要哪些权限才能访问
@Service
public class UrlService {
@Autowired
RoseService roseService;
public String[] getRose(String url) {
if (url.equals("user")) {
return new String[]{roseService.USER};
}
if (url.equals("admin")) {
return new String[]{roseService.ADMIN, roseService.USER};
}
return new String[]{};
}
}
(3)、用户对象,必须实现Security的UserDetails的6个方法。
@Component
public class UserEntity implements UserDetails {
private String username;
private String password;
private String[] roses;
@Override
public Collection extends GrantedAuthority> getAuthorities() {
List authorities = new ArrayList<>();
for (String rose : roses) {
authorities.add(new SimpleGrantedAuthority(rose));
}
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setRose(String[] roses) {
this.roses = roses;
}
}
(4)、用户管理类,必须实现Security的UserDatailsService的loadUserByUsername方法,用户登录的时候Security会自动调用该方法
@Service
public class UserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity user = getUser(username);
if (null == user) {
throw new UsernameNotFoundException("账户不存在!");
}
return user;
}
public UserEntity getUser(String username) {
UserEntity user = null;
if (username.equals("admin")) {
user = new UserEntity();
user.setUsername("admin");
user.setPassword(new BCryptPasswordEncoder().encode("123456"));
user.setRose(new String[]{"ADMIN"});
}
if (username.equals("user")) {
user = new UserEntity();
user.setUsername("user");
user.setPassword(new BCryptPasswordEncoder().encode("123456"));
user.setRose(new String[]{"USER"});
}
return null;
}
}
4、一个简单的Controller
设定是这样的,open-不需要权限,admin支持ADMIN权限,user支持ADMIN、USER权限。
@RestController
public class TestController {
@RequestMapping(value = "open")
public Object open() {
return "open";
}
@RequestMapping(value = "admin")
public Object admin() {
return "admin";
}
@RequestMapping(value = "user")
public Object user() {
return "user";
}
}
二、开搞
需要准备最少三个类。
1、WebSecurityFilterInvocationSecuritymetadataSource用于根据Url获取到该Url需要什么样的权限才能访问
@Component
public class WebSecurityFilterInvocationSecuritymetadataSource implements FilterInvocationSecuritymetadataSource {
@Autowired
UrlService urlService;
private static Map roseMap;
@PostConstruct
private List getRoses() {
System.out.println("Url权限数据已加载");
roseMap = new HashMap<>();
roseMap.put("/admin", urlService.getRose("admin"));
roseMap.put("/user", urlService.getRose("user"));
return null;
}
@Override
public Collection getAttributes(Object o) throws IllegalArgumentException {
String url = ((FilterInvocation) o).getRequestUrl();
System.out.println("当前请求的Url:" + url);
String[] roses = roseMap.get(url);
return SecurityConfig.createList(roses);
}
@Override
public Collection getAllConfigAttributes() {
System.out.println("返回所有定义好的权限资源");
return null;
}
@Override
public boolean supports(Class> aClass) {
System.out.println("是否支持校验");
return true;
}
}
2、WebSecurityAccessDecisionManger
用于权限校验,对比传过来的Url需要的权限和当前用户拥有的权限去判定当前用户对当前Url是否有权访问
@Component
public class WebSecurityAccessDecisionManger implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection collection) throws AccessDeniedException, InsufficientAuthenticationException {
//如果authentication是UsernamePasswordAuthenticationToken实例,说明当前用户已登录。
if (!(authentication instanceof UsernamePasswordAuthenticationToken)) {
System.out.println("----------------> 请先登录!");
throw new AccessDeniedException("请先登录");
}
Collection extends GrantedAuthority> auths = authentication.getAuthorities();
System.out.println("Url需要的权限:" + collection.toString());
System.out.println("用户当前的权限:" + auths.toString());
for (ConfigAttribute configAttribute : collection) {
//循环校验,有权限满足就证明是可以访问的
for (GrantedAuthority authority : auths) {
if (configAttribute.getAttribute().equals(authority.getAuthority())) {
return;
}
}
}
System.out.println("----------------> 权限不足!");
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
System.out.println("configAttribute是否支持校验");
return true;
}
@Override
public boolean supports(Class> aClass) {
System.out.println("aClass是否支持校验");
return true;
}
}
3、WebSecurityConfig
最核心的Security配置类
@Component
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/500").permitAll()//不过滤
.antMatchers("/403").permitAll()//不过滤
.antMatchers("/404").permitAll()//不过滤
//其他不过滤的Url,略
.anyRequest() //任何其它请求
.authenticated() //都需要身份认证
.withObjectPostProcessor(new ObjectPostProcessor() {
@Override
public O postProcess(O object) {
//自定义权限校验
object.setSecuritymetadataSource(new WebSecurityFilterInvocationSecuritymetadataSource());
object.setAccessDecisionManager(new WebSecurityAccessDecisionManger());
return object;
}
})
.and()
// 开启表单登录,即登录页
.formLogin()
// 自定义登录页,未配置下启用默认登录页
// .loginPage("/login_page")
// 登录成功之后跳转的页面
.loginProcessingUrl("/index")
// 登录参数-用户名
.usernameParameter("username")
// 登录参数-密码
.passwordParameter("password")
// 登录成功回调函数-返回登陆成功的JSON信息
.successHandler((request, response, authentication) -> {
System.out.println("-----登录成功-----> ");
})
// 登录失败回调函数-返回登陆失败的JSON信息
.failureHandler((request, response, e) -> {
System.out.println("-----登录失败-----> ");
})
// 和登录相关的接口都不需要认证即可访问
.permitAll()
.and()
// 开启注销登录配置
.logout()
// 配置注销登录请求URL,默认为 "/logout"
.logoutUrl("/logout")
// 是否清除身份认证信息,默认为true,表示清除
.clearAuthentication(true)
// 是否使session失效,默认为true
.invalidateHttpSession(true)
// 删除指定的cookie信息,可以传入多个key
.deletecookies("JSESSIONID")
// 注销回调函数,可以处理数据清除工作
.addLogoutHandler((request, response, authentication) -> {
System.out.println("-----注销回调----->");
this.getcookieMsg(request);
})
// 注销成功回调函数
.logoutSuccessHandler((request, response, authentication) -> {
System.out.println("-----注销成功----->");
// 注销成功后重定向到登录页
response.sendRedirect("/login");
})
.and()
.csrf().disable();
// http.addFilterBefore(authorizationSecurityInterceptor, FilterSecurityInterceptor.class);
}
private void getcookieMsg(HttpServletRequest request) {
cookie[] cookies = request.getcookies();
if (null == cookies || cookies.length < 1) {
System.out.println("------> cookie已全部清除!");
} else {
System.out.println("---cookie---> ");
}
}
//解决静态资源被拦截的问题
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(15);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}



