SpringBoot中Secuuity 权限管理

    技术2022-07-15  79

    security 主要涉及模块

    security 原生类 注释 (自定义类需) AuthenticationFilter 身份验证筛选器 继承类 UsernamePasswordAuthenticationFilter AuthenticationManagerBuilder 身份验证管理器生成器 实现接口 UserDetailsService / UserDetails(通过两个类实现接口) [FilterSecurityInterceptor] AccessDecisionManager 访问决策管理器 实现接口 FilterInvocationSecurityMetadataSource SecurityMetadataSource 安全元数据源 实现接口 AccessDecisionManager

    security


    用户 UsernamePass UserDetailsService UserDetails FilterInvocation... AccessDecision user(请求) UsernamePasswordAuthenticationFilter UserDetailsService 检验(用户名,密码,验证码) username Authorities 设置用户权限 authorities.add(new SimpleGrantedAuthority (role.getName())) 登录失败信息(验证码,用户,密码错,过期等) request FilterInvocationSecurity MetadataSource 获取能访问该请求的 权限 return Collection<ConfigAttribute> authentication,configAttributes AccessDecis ionManager校 验用户是否携带(aut hentication )路径所需要(conf igAttribute s)的权限 fail:权限不足或别的错误提示 success: 渲染对应的页面 用户 UsernamePass UserDetailsService UserDetails FilterInvocation... AccessDecision Security

    securityConfig

    @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired MyUserService myUserService; @Autowired CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource; @Autowired CustomUrlDecisionManager customUrlDecisionManager; @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserService); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode","/verifyCode1"); } @Bean LoginFilter loginFilter() throws Exception { LoginFilter loginFilter = new LoginFilter(); loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> { response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); Hr hr = (Hr) authentication.getPrincipal(); hr.setPassword(null); RespBean ok = RespBean.ok("登录成功!", hr); String s = new ObjectMapper().writeValueAsString(ok); out.write(s); out.flush(); out.close(); } ); loginFilter.setAuthenticationFailureHandler((request, response, exception) -> { response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); RespBean respBean = RespBean.error(exception.getMessage()); if (exception instanceof LockedException) { respBean.setMsg("账户被锁定,请联系管理员!"); } else if (exception instanceof CredentialsExpiredException) { respBean.setMsg("密码过期,请联系管理员!"); } else if (exception instanceof AccountExpiredException) { respBean.setMsg("账户过期,请联系管理员!"); } else if (exception instanceof DisabledException) { respBean.setMsg("账户被禁用,请联系管理员!"); } else if (exception instanceof BadCredentialsException) { respBean.setMsg("用户名或者密码输入错误,请重新输入!"); } out.write(new ObjectMapper().writeValueAsString(respBean)); out.flush(); out.close(); } ); loginFilter.setAuthenticationManager(authenticationManagerBean()); loginFilter.setFilterProcessesUrl("/doLogin"); ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry()); sessionStrategy.setMaximumSessions(1); loginFilter.setSessionAuthenticationStrategy(sessionStrategy); return loginFilter; } @Bean SessionRegistryImpl sessionRegistry() { return new SessionRegistryImpl(); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O object) { object.setAccessDecisionManager(customUrlDecisionManager); object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource); return object; } }) .and() .logout() .logoutSuccessHandler((req, resp, authentication) -> { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销成功!"))); out.flush(); out.close(); } ) .permitAll() .and() .csrf().disable().exceptionHandling() //没有认证时,在这里处理结果,不要重定向 .authenticationEntryPoint((req, resp, authException) -> { resp.setContentType("application/json;charset=utf-8"); resp.setStatus(401); PrintWriter out = resp.getWriter(); RespBean respBean = RespBean.error("访问失败!"); if (authException instanceof InsufficientAuthenticationException) { respBean.setMsg("请求失败,请联系管理员!"); } out.write(new ObjectMapper().writeValueAsString(respBean)); out.flush(); out.close(); } ); http.addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event -> { HttpServletResponse resp = event.getResponse(); resp.setContentType("application/json;charset=utf-8"); resp.setStatus(401); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(RespBean.error("您已在另一台设备登录,本次登录已下线!"))); out.flush(); out.close(); }), ConcurrentSessionFilter.class); http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class); } }

    UsernamePasswordAuthenticationFilter

    只在用户初次登录时访问一次;进行验证码的校验

    public class LoginFilter extends UsernamePasswordAuthenticationFilter { @Autowired SessionRegistry sessionRegistry; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String verify_code = (String) request.getSession().getAttribute("verify_code"); if (request.getContentType().contains(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().contains(MediaType.APPLICATION_JSON_UTF8_VALUE)) { Map<String, String> loginData = new HashMap<>(); try { loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class); } catch (IOException e) { }finally { String code = loginData.get("code"); checkCode(response, code, verify_code); } String username = loginData.get(getUsernameParameter()); String password = loginData.get(getPasswordParameter()); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); setDetails(request, authRequest); Hr principal = new Hr(); principal.setUsername(username); sessionRegistry.registerNewSession(request.getSession(true).getId(), principal); return this.getAuthenticationManager().authenticate(authRequest); } else { checkCode(response, request.getParameter("code"), verify_code); return super.attemptAuthentication(request, response); } } public void checkCode(HttpServletResponse resp, String code, String verify_code) { if (code == null || verify_code == null || "".equals(code) || !verify_code.toLowerCase().equals(code.toLowerCase())) { //验证码不正确 throw new AuthenticationServiceException("验证码不正确"); } } }

    MyUserService

    只在用户初次登录时访问一次;进行检测用户存在否;同时给用户注入权限。

    public class MyUserService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user= UserMapper.loadUserByUsername(username); if (user== null) { throw new UsernameNotFoundException("用户名不存在!"); } user.setRoles(userMapper.getHrRolesById(user.getId())); return user; } }

    User

    只在用户初次登录时访问一次;将用户所具备的权限 注册到security中交由容器管理。

    public class User implements UserDetails { @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user= (User ) o; return Objects.equals(username, user .username); } @Override public int hashCode() { return Objects.hash(username); } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } @Override @JsonIgnore public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authorities = new ArrayList<>(roles.size()); for (Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } }

    FilterInvocationSecurityMetadataSource

    每次各种请求都会进行拦截;检测出此次请求所需要的权限。

    @Component public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Autowired MenuService menuService; AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { String requestUrl = ((FilterInvocation) object).getRequestUrl(); List<Menu> menus = menuService.getAllMenusWithRole(); for (Menu menu : menus) { if (antPathMatcher.match(menu.getUrl(), requestUrl)) { List<Role> roles = menu.getRoles(); String[] str = new String[roles.size()]; for (int i = 0; i < roles.size(); i++) { str[i] = roles.get(i).getName(); } //将可以访问这个路径的角色 封装 return SecurityConfig.createList(str); } } return SecurityConfig.createList("ROLE_LOGIN"); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return true; } }

    AccessDecisionManager

    当解析出此次请求所需要的条件后,根据所需的权限校验当前用户是否携带有此次请求所需要的权限 从而实现权限管理。

    @Component public class CustomUrlDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { for (ConfigAttribute configAttribute : configAttributes) {// 获取可以访问当前路径的权限 String needRole = configAttribute.getAttribute(); if ("ROLE_LOGIN".equals(needRole)) { if (authentication instanceof AnonymousAuthenticationToken) { throw new AccessDeniedException("尚未登录,请登录!"); }else { return; } } Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();//获取当前用户携带的角色权限 for (GrantedAuthority authority : authorities) { if (authority.getAuthority().equals(needRole)) { return; } } } throw new AccessDeniedException("权限不足,请联系管理员!"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } }
    Processed: 0.008, SQL: 9