zoukankan      html  css  js  c++  java
  • SpringSecurity配置,简单梳理

    生活加油:摘一句子:

    “我希望自己能写这样的诗。我希望自己也是一颗星星。如果我会发光,就不必害怕黑暗。如果我自己是那么美好,那么一切恐惧就可以烟消云散。于是我开始存下了一点希望—如果我能做到,那么我就战胜了寂寞的命运。”

                                                                                                                                                    -----------------------------王小波《我在荒岛上迎接黎明》
    ————————————————
    嗯,刚学的时候,一大堆,明白是配置了啥,不知道为啥那么配,嗯,今天把配置思想和小伙伴分享,是很简单的那种,不足之处请留言.

    SpringSecurity的配置基于WebSecurityConfigurerAdapter的实现类,我们这里主要讲基本配置,即configure(HttpSecurity http)方法的配置,其实大都有默认值,我们可以直接用默认值,也可以自己设置.

    私以为简单配置三点:

    • 认证(SpringSecurity在第一次登录做认证,这时候只进行认证,不进行授权(鉴权处理))
      • 表单认证:涉及到的配置包括前端登录请求的url,登录失败,登录成功返回的页面url,已及表单返回字段的用户名密码K的设置,还有登陆成功回调处理登录失败回调处理.这里要注意登录数据是通过key/value的形式来传递.
      • 自定义认证,(验证码):自定义验证采用,在自定义过滤器的方式,在最开始插入一个过滤器链,需要自定义异常,自定义失败登录失败处理器,注意这里的失败处理器要和表单登录使用同一个失败处理器.
    • 授权(基于RBAC权限控制):
      • 授权这样分析基于RBAC的权限控制,即用户分配角色,角色分配权限.需要配置根据访问路径得到需要的角色的处理类(FilterlnvocationSecurityrMetadataSource),根据返回角色进行鉴权处理类(AccessDecisionManager) , 同时判断是否登录,还需要配置认证失败的处理器.
    • 注销登录
      • 注销登录默认的url为"/logout".需要配置,注销登录处理器

    配置图:

     SpringSecurity简单配置图:

     下面就上面讲的代码分析

    一.认证:

    1.configure(HttpSecurity http)方法的配置:

    package com.liruilong.hros.config;
     
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.liruilong.hros.filter.VerifyCodeFilter;
    import com.liruilong.hros.model.Hr;
    import com.liruilong.hros.model.RespBean;
    import com.liruilong.hros.service.HrService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.*;
    import org.springframework.security.config.annotation.ObjectPostProcessor;
    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.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
     
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
     
    /**
     * @Description :
     * @Author: Liruilong
     * @Date: 2019/12/18 19:11
     */
     
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        HrService hrService;
        @Autowired
        CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;
        @Autowired
        CustomUrlDecisionManager customUrlDecisionManager;
         @Autowired
        VerifyCodeFilter verifyCodeFilter ;
        @Autowired
        MyAuthenticationFailureHandler myAuthenticationFailureHandler;
     
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class)
                    .authorizeRequests()
                    //.anyRequest().authenticated()
                    //所有请求的都会经过这进行鉴权处理。返回当前请求需要的角色。
                    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                        @Override
                        public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                            object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                            object.setAccessDecisionManager(customUrlDecisionManager);
                            return object;
                        }
                    })
                    .and().formLogin().usernameParameter("username").passwordParameter("password")
                    //设置登录请求的url路径
                    .loginProcessingUrl("/doLogin")
                    /*需要身份验证时,将浏览器重定向到/ login
                    我们负责在请求/ login时呈现登录页面
                    当身份验证尝试失败时,将浏览器重定向到/ login?error(因为我们没有另外指定)
                    当请求/ login?error时,我们负责呈现失败页面
                    成功注销后,将浏览器重定向到/ login?logout(因为我们没有另外指定)
                    我们负责在请求/ login?logout时呈现注销确认页面*/
                    .loginPage("/login")
                    //登录成功回调
                    .successHandler(new AuthenticationSuccessHandler() {
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                            httpServletResponse.setContentType("application/json;charset=utf-8");
                            PrintWriter out = httpServletResponse.getWriter();
                            Hr hr = (Hr) authentication.getPrincipal();
                            //密码不回传
                            hr.setPassword(null);
                            RespBean ok = RespBean.ok("登录成功!", hr);
                            //将hr转化为Sting
                            String s = new ObjectMapper().writeValueAsString(ok);
                            out.write(s);
                            out.flush();
                            out.close();
                        }
                    })
                    //登失败回调
                    .failureHandler(myAuthenticationFailureHandler)
                    //相关的接口直接返回
                    .permitAll().and().logout()
                    //注销登录
                    .logoutSuccessHandler(new LogoutSuccessHandler() {
                        @Override
                        public void onLogoutSuccess(HttpServletRequest httpServletRequest,
                                                    HttpServletResponse httpServletResponse,
                                                    Authentication authentication) throws IOException, ServletException {
                            httpServletResponse.setContentType("application/json;charset=utf-8");
                            PrintWriter out = httpServletResponse.getWriter();
                            out.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销成功!")));
                            out.flush();
                            out.close();
                        }
                    })
                    .permitAll().and().csrf().disable().exceptionHandling()
                    //没有认证时,在这里处理结果,不要重定向
                    .authenticationEntryPoint(
                            //myAuthenticationEntryPoint;
                            new AuthenticationEntryPoint() {
                        @Override
                        public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException authException) throws IOException, ServletException {
                            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();
                        }
                    });
        }
        @Bean
        PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(hrService);
        }
        /**
         * @Author Liruilong
         * @Description  放行的请求路径
         * @Date 19:25 2020/2/7
         * @Param [web]
         * @return void
         **/
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/auth/code","/login","/css/**","/js/**", "/index.html", "/img/**", "/fonts/**","/favicon.ico");
        }
    }
     
     

    2.失败处理器定义:

    package com.liruilong.hros.config;
     
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.liruilong.hros.Exception.ValidateCodeException;
    import com.liruilong.hros.model.RespBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.authentication.*;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.stereotype.Component;
     
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
     
    /**
     * @Description :
     * @Author: Liruilong
     * @Date: 2020/2/11 23:08
     */
    @Component
    public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
            httpServletResponse.setContentType("application/json;charset=utf-8");
            PrintWriter out = httpServletResponse.getWriter();
            RespBean respBean = RespBean.error(e.getMessage());
               // 验证码自定义异常的处理
            if (e instanceof ValidateCodeException){
                respBean.setMsg(e.getMessage());
                //Security内置的异常处理
            }else if (e instanceof LockedException) {
                respBean.setMsg("账户被锁定请联系管理员!");
            } else if (e instanceof CredentialsExpiredException) {
                respBean.setMsg("密码过期请联系管理员!");
            } else if (e instanceof AccountExpiredException) {
                respBean.setMsg("账户过期请联系管理员!");
            } else if (e instanceof DisabledException) {
                respBean.setMsg("账户被禁用请联系管理员!");
            } else if (e instanceof BadCredentialsException) {
                respBean.setMsg("用户名密码输入错误,请重新输入!");
            }
            //将hr转化为Sting
            out.write(new ObjectMapper().writeValueAsString(respBean));
            out.flush();
            out.close();
        }
        @Bean
        public  MyAuthenticationFailureHandler getMyAuthenticationFailureHandler(){
            return new MyAuthenticationFailureHandler();
        }
    }

    3.前端将JSON数据转换为K-V形式:

    //post请求的封装K-v形式
    let base = '';
    export const postKeyValueRequest = (url, params) => {
        return axios({
            method: 'post',
            url: `${base}${url}`,
            data: params,
            transformRequest: [function (data) {
                let ret = ''
                for (let it in data) {
                    ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
                }
    
                return ret
            }],
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            }
        });
    }

     4.自定义过滤器:

    package com.liruilong.hros.filter;
     
     
    import com.liruilong.hros.Exception.ValidateCodeException;
    import com.liruilong.hros.config.MyAuthenticationFailureHandler;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;
     
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
     
     
    /**
     * @Description :
     * @Author: Liruilong
     * @Date: 2020/2/7 19:39
     */
    @Component
    public class VerifyCodeFilter extends OncePerRequestFilter {
     
        @Bean
        public VerifyCodeFilter getVerifyCodeFilter() {
            return new VerifyCodeFilter();
        }
        @Autowired
        MyAuthenticationFailureHandler myAuthenticationFailureHandler;
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
     
            if (StringUtils.equals("/doLogin", request.getRequestURI())
                    && StringUtils.equalsIgnoreCase(request.getMethod(), "post")) {
                // 1. 进行验证码的校验
                try {
                String requestCaptcha = request.getParameter("code");
                    if (requestCaptcha == null) {
                        throw new ValidateCodeException("验证码不存在");
                    }
                String code = (String) request.getSession().getAttribute("yanzhengma");
                    if (StringUtils.isBlank(code)) {
                        throw new ValidateCodeException("验证码过期!");
                    }
                code = code.equals("0.0") ? "0" : code;
                logger.info("开始校验验证码,生成的验证码为:" + code + " ,输入的验证码为:" + requestCaptcha);
                    if (!StringUtils.equals(code, requestCaptcha)) {
                        throw new ValidateCodeException("验证码不匹配");
                    }
                } catch (AuthenticationException e) {
                    // 2. 捕获步骤1中校验出现异常,交给失败处理类进行进行处理
                    myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
                } finally {
                    filterChain.doFilter(request, response);
                }
            } else {
                filterChain.doFilter(request, response);
            }
     
     
        }
     
    }

     5.自定义异常:

    package com.liruilong.hros.Exception;
     
     
    import org.springframework.security.core.AuthenticationException;
     
     
    /**
     * @Description :
     * @Author: Liruilong
     * @Date: 2020/2/8 7:24
     */
     
    public class ValidateCodeException extends AuthenticationException  {
     
        public ValidateCodeException(String msg) {
            super(msg);
        }
     
     
    }

     二授权:

    RBAC模型实体图

     1.开发者自定义 FilterlnvocationSecurityrMetadataSource

    package com.liruilong.hros.config;
     
    import com.liruilong.hros.mapper.MenuMapper;
    import com.liruilong.hros.model.Menu;
    import com.liruilong.hros.model.Role;
    import com.liruilong.hros.service.MenuService;
    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.stereotype.Component;
    import org.springframework.util.AntPathMatcher;
     
    import java.util.Collection;
    import java.util.List;
     
    /**
     * @Description : 权限处理,根据请求,分析需要的角色,该类的主要功能就是通过当前的请求地址,获取该地址需要的用户角色
     * @Author: Liruilong
     * @Date: 2019/12/24 12:17
     */
    @Component
    public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
        @Autowired
        MenuService menuService;
        //路径比较工具
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        /**
         * @return java.util.Collection<org.springframework.security.access.ConfigAttribute>
         * @Author Liruilong
         * @Description 当前请求需要的角色
         * @Date 18:13 2019/12/24
         * @Param [object]
         **/
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            //获取当前请求路径
            String requestUrl = ((FilterInvocation) object).getRequestUrl();
            //获取所有的菜单url路径
            List<Menu> menus = menuService.getAllMenusWithRole();
            for (Menu menu : menus) {
                if (antPathMatcher.match(menu.getUrl(), requestUrl)) {
                    //拥有当前菜单权限的角色
                    List<Role> roles = menu.getRoles();
                    String[] strings = new String[roles.size()];
                    for (int i = 0; i < roles.size(); i++) {
                        strings[i] = roles.get(i).getName();
                    }
                    return SecurityConfig.createList(strings);
                }
            }
            // 没匹配上的资源都是登录
            return SecurityConfig.createList("ROLE_LOGIN");
        }
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
     
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    }

    2.自定义 AccessDecisionManager

    package com.liruilong.hros.config;
     
    import org.springframework.beans.factory.annotation.Autowired;
    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.stereotype.Component;
     
    import java.util.Collection;
     
    /**
     * @Description : 判断当前用户是否具备菜单访问,当一个请求走完 FilterlnvocationSecurityMetadataSource 中的 getAttributes 方法后,接下来就会 来到 AccessDecisionManager 类中进行角色信息的比对
     * @Author: Liruilong
     * @Date: 2019/12/24 19:12
     */
    @Component
    public class CustomUrlDecisionManager implements AccessDecisionManager {
     
        /**
         * @return void
         * @Author Liruilong
         * @Description decide 方法有三个参数, 第一个参数包含当前登录用户的信息;
         * 第二个参数则是一个 Filterlnvocation 对 象 ,可以 获 取当前请求对 象等;
         * 第 三个参 数就是 FilterlnvocationSecurityMetadataSource 中的 getAttributes 方法的返回值, 即当前请求 URL 所 需要的角色。
         * @Date 18:28 2020/2/13
         * @Param [authentication, object, configAttributes]
         **/
     
        @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;
        }
    }
    // authorities.stream().anyMatch((authority) ->authority.getAuthority().equals(attribute));

     

     

     RBAC模型分析

    3. 当然还需要用户类去实现UserDatails接口

    package com.liruilong.hros.model;
     
     
    import com.fasterxml.jackson.annotation.JacksonInject;
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import com.fasterxml.jackson.annotation.JsonIgnoreType;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
     
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
     
    public class Hr implements UserDetails {
        private Integer id;
     
        private String name;
     
        private String phone;
     
        private String telephone;
     
        private String address;
     
        private Boolean enabled;
     
        private String username;
     
        private String password;
     
        private String userface;
     
        private String remark;
     
        private List<Role> roles;
     
        public Integer getId() {
            return id;
        }
     
        public void setId(Integer id) {
            this.id = id;
        }
     
        public String getName() {
            return name;
        }
     
        public void setName(String name) {
            this.name = name == null ? null : name.trim();
        }
     
        public String getPhone() {
            return phone;
        }
     
        public void setPhone(String phone) {
            this.phone = phone == null ? null : phone.trim();
        }
     
        public String getTelephone() {
            return telephone;
        }
     
        public void setTelephone(String telephone) {
            this.telephone = telephone == null ? null : telephone.trim();
        }
     
        public String getAddress() {
            return address;
        }
     
        public void setAddress(String address) {
            this.address = address == null ? null : address.trim();
        }
     
     
     
        public void setEnabled(Boolean enabled) {
            this.enabled = enabled;
        }
     
        @Override
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username == null ? null : username.trim();
        }
     
        @Override
        public String getPassword() {
            return password;
        }
     
        public void setPassword(String password) {
            this.password = password == null ? null : password.trim();
        }
     
        public String getUserface() {
            return userface;
        }
     
        public void setUserface(String userface) {
            this.userface = userface == null ? null : userface.trim();
        }
     
        public String getRemark() {
            return remark;
        }
     
        public void setRemark(String remark) {
            this.remark = remark == null ? null : remark.trim();
        }
     
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
     
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
     
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
     
        @Override
        public boolean isEnabled() {
            return enabled;
        }
     
     
        @Override
        @JsonIgnore
        public Collection<? extends GrantedAuthority> getAuthorities() {
          List<SimpleGrantedAuthority> authorities = new ArrayList<>(roles.size());
          roles.stream().forEach( (role) ->authorities.add(new SimpleGrantedAuthority(role.getName())));
            return authorities;
        }
     
        public List<Role> getRoles() {
            return roles;
        }
     
        public void setRoles(List<Role> roles) {
            this.roles = roles;
        }
    }

    _______________________________________________________________________________________________________

    嗯.以上就是对SpringSecurity配置的理解,不足之处请小伙伴留言/生活加油!

  • 相关阅读:
    远程办公的一天:魔幻24小时
    LVS:三种负载均衡方式比较
    程序员的二十句励志名言,看看你最喜欢哪句?
    个人服务器开通~
    jquery大全
    CSS大全
    英语中的连词说明
    高版本SqlServer转低版本SqlServer经验总结
    SQLServer中,sa帐号旁边有个小红箭头
    Entity Framework GroupBy usage
  • 原文地址:https://www.cnblogs.com/liruilong/p/12322888.html
Copyright © 2011-2022 走看看