zoukankan      html  css  js  c++  java
  • spring security认证

    1 开发基于表单的认证

    Spring security核心的功能

    • 认证(你是谁?)
    • 授权(你能干什么?)
    • 攻击防护(防止伪造身份)

    spring security实现了默认的用户名+密码认证,默认用户名为user,密码为:

     spring security基本原理:过滤器链

       对于UsernamePasswordAuthenticationFilter只会拦截 url为/login,method为POST的请求。

     1.1 自定义用户认证逻辑

    1)处理用户信息获取逻辑

      UserDetailsService接口,只有一个方法:loadUserByUsername

    实现该接口:数据库中存放的是加密密码,对于同一个密码不同时间的加密密文不一样

    @Component
    public class MyUserDetailsService implements UserDetailsService {
        private Logger logger = LoggerFactory.getLogger(getClass());
        @Autowired
        private PasswordEncoder passwordEncoder;
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            logger.info("用户名信息:" + s);
            // 根据用户名查找用户信息
            logger.info("数据库密码:" + passwordEncoder.encode("123456"));
            // 用户名和密码信息用来做认证,权限信息用来对该用户做授权
            return new User(s, "$2a$10$eFw06n0ABK2NFuse8y5f/eDUq7we26qQTceEtXSWNbMXnQ5Yf5Iha",
                    AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
        }
    }

    2)处理用户信息校验逻辑

      处理密码加密解密:在配置文件中将PasswordEncoder对象注入spring容器,等价于@Component+包扫描组件

     

    1.2 个性化用户认证流程

    1)对于浏览器,返回自定义登录页面,让UsernamePasswordXxxFilter来处理登录请求;对于调用RESTful服务,返回json错误信息。

       用户登录成功后 ,对于浏览器,返回需要的页面;对于服务,返回json数据。

       权限配置:

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin()// 表单登录
                    .loginPage("/authentication/require") //将登录页面重定向到controller
                    .loginProcessingUrl("/authentication/form")
                    .and()
                    .authorizeRequests() //请求授权
                    .antMatchers("/authentication/require",
                            securityProperties.getBrowser().getLoginPage()).permitAll()//该页面允许通过
                    .anyRequest()
                    .authenticated()  // 其他资源需要认证
                    .and()
                    .csrf().disable();  // 将跨站防护关掉
        }

      

      控制器,根据之前URL的路径判断是否为RESTful服务,在处理

    /*
    当客户端发出请求,当需要认证时,spring security会重定向到该控制器
     */
    @RestController
    public class BrowserSecurityController {
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        // 请求缓存
        private RequestCache requestCache = new HttpSessionRequestCache();
        private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
        @Autowired
        private SecurityProperties securityProperties;
        /**
         * 当需要身份认证时跳转到这里
         * @param request
         * @param response
         * @return
         */
        @RequestMapping("/authentication/require")
        @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
        public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
            // 判断请求类型,HTML或者app
            SavedRequest savedRequest = requestCache.getRequest(request, response);
            if(savedRequest!=null){
                String targetUrl = savedRequest.getRedirectUrl();
                logger.info("引发跳转的URL:"+targetUrl);
                // 如果之前的URL为.html结尾的URL,则重定向到登录页面
                if(StringUtils.endsWithIgnoreCase(targetUrl, ".html")){
                    redirectStrategy.sendRedirect(request, response,
                            securityProperties.getBrowser().getLoginPage());
                }
            }
            return new SimpleResponse("请求的服务需要身份认证,请引导用户到登录页面");
        }
    }
    BrowserSecurityController.java

      在启动项目中的application.properties文件中配置登录页面:

    # 配置登录页面
    getword.security.browser.loginPage=/demo.html

      读取配置文件信息:

      

    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    // 读取前缀为getword.security的属性配置,其中browser中的属性会被读取到browserProperties中
    @ConfigurationProperties(prefix = "getword.security")
    public class SecurityProperties {
        // browser的属性会匹配getword.security.browser后面的属性
        private BrowserProperties browser = new BrowserProperties();
    
        public BrowserProperties getBrowser() {
            return browser;
        }
    
        public void setBrowser(BrowserProperties browser) {
            this.browser = browser;
        }
    }
    SecurityProperties.java
    public class BrowserProperties {
        private String loginPage = "/login.html"; //默认值
    
        public String getLoginPage() {
            return loginPage;
        }
    
        public void setLoginPage(String loginPage) {
            this.loginPage = loginPage;
        }
    }
    BrowserProperties.java
    @Configuration
    @EnableConfigurationProperties(SecurityProperties.class) //让属性配置读取器生效
    public class SecurityCodeConfig {
    }

    2)自定义登录成功处理,异步登录,AuthenticationSuccessHandler接口

      自定义登录成处理:

    @Component("vstudyAuthenticationSuccessHandler")
    public class VstudyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
        private Logger logger = LoggerFactory.getLogger(getClass());
        //工具类, 将对象转成json
        @Autowired
        private ObjectMapper objectMapper;
        // 登录成功后调用
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            logger.info("登录成功");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        }
    }
    VstudyAuthenticationSuccessHandler

      注册,使处理器生效:

    3)登录失败处理

    @Component("vstudyAuthenticationFailHandler")
    public class VstudyAuthenticationFailHandler implements AuthenticationFailureHandler {
        @Autowired
        private ObjectMapper objectMapper;
    
        private Logger logger = LoggerFactory.getLogger(getClass());
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                            AuthenticationException e) throws IOException, ServletException {
            logger.info("登录失败");
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); //服务器内部错误
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(objectMapper.writeValueAsString(e));
        }
    }
    VstudyAuthenticationFailHandler.java

    配置:和success类似

    4)判断请求方式,做出相应的处理

    successHandler:

    @Component("vstudyAuthenticationSuccessHandler")
    public class VstudyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
        private Logger logger = LoggerFactory.getLogger(getClass());
        //工具类, 将对象转成json
        @Autowired
        private ObjectMapper objectMapper;
    
        @Autowired
        private SecurityProperties securityProperties; //获取配置信息
    
        // 登录成功后调用
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            logger.info("登录成功");
            if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write(objectMapper.writeValueAsString(authentication));
            }else{
                // 调用父类方法,完成重定向跳转
                super.onAuthenticationSuccess(request, response, authentication);
            }
        }
    }
    VstudyAuthenticationSuccessHandler

    failureHandler:

    @Component("vstudyAuthenticationFailHandler")
    public class VstudyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
        @Autowired
        private SecurityProperties securityProperties;
        @Autowired
        private ObjectMapper objectMapper;
    
        private Logger logger = LoggerFactory.getLogger(getClass());
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                            AuthenticationException e) throws IOException, ServletException {
            logger.info("登录失败");
            if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); //服务器内部错误
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write(objectMapper.writeValueAsString(e));
            }else{
                super.onAuthenticationFailure(request, response, e);
            }
        }
    }
    VstudyAuthenticationFailHandler

    2 认证流程

    3 图形验证码

    3.1 生成图形验证码

      验证码图片信息:

    public class ImageCode {
        private BufferedImage image;
        private String code;
        private LocalDateTime expireTime;//过期时间
        public ImageCode(BufferedImage image, String code, int expireIn){
            this.image = image;
            this.code = code;
            this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
        }
    
        public ImageCode(BufferedImage image, String code, LocalDateTime expireTime){
            this.image = image;
            this.code = code;
            this.expireTime = expireTime;
        }
    }
    ImageCode

      控制器:

    @RestController
    public class ValidateCodeController {
        public static String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
        @Autowired
        private SecurityProperties securityProperties;
    
        @GetMapping("/image/code")
        public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
            ImageCode imageCode = createImageCode(new ServletWebRequest(request,response));
            sessionStrategy.setAttribute(new ServletWebRequest(request, response), SESSION_KEY, imageCode);
            ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
        }
    
        /**
         * 生成ImageCode验证码
         * @param request
         * @return
         */
        public ImageCode createImageCode(ServletWebRequest request){
            // 生成验证码,方法很多
            int width = 60;
            int height = 20;
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    
            Graphics g = image.getGraphics();
    
            Random random = new Random();
    
            g.setColor(getRandColor(200, 250));
            g.fillRect(0, 0, width, height);
            g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
            g.setColor(getRandColor(160, 200));
            for (int i = 0; i < 155; i++) {
                int x = random.nextInt(width);
                int y = random.nextInt(height);
                int xl = random.nextInt(12);
                int yl = random.nextInt(12);
                g.drawLine(x, y, x + xl, y + yl);
            }
    
            String sRand = "";
            for (int i = 0; i < 4; i++) {
                String rand = String.valueOf(random.nextInt(10));
                sRand += rand;
                g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
                g.drawString(rand, 13 * i + 6, 16);
            }
    
            g.dispose();
    
            return new ImageCode(image, sRand, 100);
        }
        /**
         * 生成随机背景条纹
         *
         * @param fc
         * @param bc
         * @return
         */
        private Color getRandColor(int fc, int bc) {
            Random random = new Random();
            if (fc > 255) {
                fc = 255;
            }
            if (bc > 255) {
                bc = 255;
            }
            int r = fc + random.nextInt(bc - fc);
            int g = fc + random.nextInt(bc - fc);
            int b = fc + random.nextInt(bc - fc);
            return new Color(r, g, b);
        }
    }
    ValidateCodeController

    3.2 验证码校验

     自定义过滤器:

    public class ValidateCodeFilter extends OncePerRequestFilter {
        /**
         * 验证码校验失败处理器
         */
        private AuthenticationFailureHandler authenticationFailureHandler;
        /**
         * 系统配置信息
         */
        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
        /**
         * 系统中的校验码处理器
         */
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
            // 只处理登录请求
            if (StringUtils.equals("/authetication/form", request.getRequestURI())
                    && StringUtils.equalsIgnoreCase(request.getMethod(), "POST")) {
                try {
                    logger.info("验证码校验通过");
                } catch (ValidateCodeException e) {
                    //验证失败
                    authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                }
            }
    
            filterChain.doFilter(request, response);
        }
        protected void validate(ServletWebRequest request) throws ServletRequestBindingException {
            // 从session中拿到imageCode
            ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);
            // 获取客户端输入的code,当前请求参数
            String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
            if(StringUtils.isBlank(codeInRequest)){
                throw new ValidateCodeException("验证码不能为空");
            }
            if(codeInSession==null){
                throw new ValidateCodeException("验证码不存在");
            }
            if(codeInSession.isExpired()){
                throw new ValidateCodeException("验证码已过期");
            }
            if(!StringUtils.equalsIgnoreCase(codeInSession.getCode(), codeInRequest)){
                throw new ValidateCodeException("验证码不匹配");
            }
            sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
        }
    
        public AuthenticationFailureHandler getAuthenticationFailureHandler() {
            return authenticationFailureHandler;
        }
    
        public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
            this.authenticationFailureHandler = authenticationFailureHandler;
        }
    }
    ValidateCodeFilter

    配置:

    执行流程:

     /index.html ->redirect ->/authentication/require(控制器,判断是.html结尾)->login.html ->ValidateCodeFilter ->exception -> VstudyAuthenticationFailHandler ->loginType:JSON

    login.html中,使用ajax发送登录请求 -> 验证码过滤器通过 -> UsernamePasswordFilter通过 -> 返回登录结果信息

    3.3 验证码的接口

    • 为了方便修改验证码的参数,如宽度、高度、长度等信息,我们将通过配置文件的形式配置这些信息。还有验证码的URL地址。
    • 验证码拦截的接口可配置,比如为了限制用户操作频率,对用户操作使用验证码进行限制。验证码过滤器可以拦截多个控制器请求。
    • 验证码的生成逻辑可以配置

    三级配置:

     1)验证码参数:

      默认配置:

    /**
     * 图形验证码
     */
    public class ImageCodeProperties {
        private int width = 67;
        private int height = 23;
        private int len = 4;
        private int expireIn = 60; //60秒后过期
    //seter getter
    }

     验证码信息:

    /**
     * 验证码:包括图形验证码、短信验证码
     */
    public class ValidateCodeProperties {
        private ImageCodeProperties image;
    }

     属性读取:

    @ConfigurationProperties(prefix = "getword.security")
    public class SecurityProperties {
        // browser的属性会匹配getword.security.browser后面的属性
        private BrowserProperties browser = new BrowserProperties();
    
        // 验证码属性匹配getword.security.code后面的属性
        private ValidateCodeProperties code = new ValidateCodeProperties();
    }

    end

  • 相关阅读:
    java类加载机制
    java反射
    java注解
    设计模式 单例模式
    #1015 : KMP算法
    idea 快捷键
    基础数据类型综合
    工厂模式 VS 策略模式
    AtomicI 多线程中的原子操作
    ThreadLocal<T>
  • 原文地址:https://www.cnblogs.com/zhuxiang1633/p/10311320.html
Copyright © 2011-2022 走看看