Spring Security 前后端分离下的权限控制

Spring Security 前后端分离下的权限控制

简单来说,前后端分离的项目和前后端不分离的项目区别在于:
前者是通过响应状态来驱动业务的,而后者可以直接进行页面的跳转,进而驱动业务的。

依赖

<!--SpringBoot Security模块-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>


<!-- kaptcha验证码 -->
<dependency>
  <groupId>com.github.penggle</groupId>
  <artifactId>kaptcha</artifactId>
  <version>2.3.2</version>
</dependency>

<!--redis-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.9.0</version>
</dependency>

这里使用redis,实现用户令牌token的免密登录。

SecurityConfiguration:springsecurity的配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)  //  启用方法级别的权限认证
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    MyAuthenctiationEntryPointHandler myAuthenctiationEntryPointHandler;//未登录
    @Autowired
    MyAuthenctiationSuccessHandler myAuthenctiationSuccessHandler;//登陆成功
    @Autowired
    MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;//登录失败
    @Autowired
    MyAuthenctiationDeniedHandler myAuthenctiationDeniedHandler;//无权访问
    @Autowired
    MyAuthenctiationLogoutSuccessHandler myAuthenctiationLogoutSuccessHandler;//退出成功
    @Autowired
    MyAuthenctiationInvalidSessionStrategy mMyAuthenctiationInvalidSessionStrategy;//session到期
    @Autowired
    MyAuthenctiationSessionInformationExpiredStrategy myAuthenctiationSessionStrategy;//session到期,被登陆


    //注入封装账号信息的处理bean
    @Bean
    UserDetailsService detailsService() {
        return new AuthUserDetailsService();
    }


    /*配置用户登录拦截器,解决username无法获取的问题*/
    @Bean
    LoginAuthenticationFilter loginAuthenticationFilter() throws Exception {
        LoginAuthenticationFilter filter = new LoginAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManagerBean());
        filter.setAuthenticationSuccessHandler(myAuthenctiationSuccessHandler);//登录成功
        filter.setAuthenticationFailureHandler(myAuthenctiationFailureHandler);//登录失败
        return filter;
    }




    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/attendance/userinfo/loginValidateCode","/swagger-ui.html","/swagger-resources/configuration/security","/swagger-resources/configuration/ui","/swagger*//**","/api/v1/login","/v2/api-docs", "/configuration/ui", "/swagger-resources", "/configuration/security", "/swagger-ui.html", "/webjars/**");
    }


    //用户授权操作
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("Spring Security 被启用");
        http
                .addFilterAt(loginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers("/attendance/userinfo/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(myAuthenctiationEntryPointHandler)//未登录402
                .accessDeniedHandler(myAuthenctiationDeniedHandler)//无权访问403
                 .and()
                .formLogin().loginPage("/attendance/userinfo/loginUser")
                .successHandler(myAuthenctiationSuccessHandler)//登陆成功200
                .failureHandler(myAuthenctiationFailureHandler)//登陆失败401
                .permitAll()
                .and()
                .sessionManagement()//session到期提示
                .invalidSessionStrategy(mMyAuthenctiationInvalidSessionStrategy)//session到期101
                .and()
                .requestCache().disable()
                .logout()
                .logoutSuccessHandler(myAuthenctiationLogoutSuccessHandler)//退出登陆200
                .and()
                .csrf().disable(); //关闭CSRF
    }


    //设置加密方式
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Bean
    public SecurityContextRepository securityContextRepository() {
        //设置对spring security的UserDetails进行session保存,这个必须要有,不然不会保存至session对应的缓存redis中
        HttpSessionSecurityContextRepository httpSessionSecurityContextRepository =
                new HttpSessionSecurityContextRepository();
        return httpSessionSecurityContextRepository;
    }


    //加入中间验证层,可实现自定义验证用户等信息
    @Bean
    public AuthenticationProvider authenticationProvider() {
        AuthenticationProvider provider =new MyAuthenticationProvider();
        return provider;
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }




}

LoginAuthenticationFilter:解决前段端分离项目中参数无法正常注入的问题

public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {


    @Autowired
    RedisTemplate redisTemplate;


    @Autowired
    UserService userService;




    public static LoginAuthenticationFilter loginAuthenticationFilter;


    // 关键3
    @PostConstruct
    public void init() {
        loginAuthenticationFilter = this;
        loginAuthenticationFilter.userService = this.userService;
    }


    public LoginAuthenticationFilter() {
        AntPathRequestMatcher requestMatcher = new AntPathRequestMatcher("/attendance/userinfo/loginUser", "POST");
        this.setRequiresAuthenticationRequestMatcher(requestMatcher);
        this.setAuthenticationManager(getAuthenticationManager());
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {//必须post登录
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        String token =null;
        String username = null;
        String password = null;
        String vercode = null;
        //以json形式处理数据
        try {
            //将请求中的数据转为map
            Map<String,String> map = new ObjectMapper().readValue(request.getInputStream(), Map.class);
            username = map.get("userName");
            password = map.get("password");
            vercode = map.get("vercode");
            token= map.get("token");
            request.getSession().setAttribute("vercode", vercode);
        } catch (IOException e) {
            e.printStackTrace();
        }


        if(token ==null){//使用密码进行登录
            //如果是application/json类型,做如下处理
            if(request.getContentType() != null && (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)||request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE))){


                if (username == null) {
                    username = "";
                }
                if (password == null) {
                    password = "";
                }
                username = username.trim();
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            }
        }else {//使用用户令牌进行登录
            String usernametemp = null;
            if(redisTemplate.opsForValue().get("token_"+token)!=null){
                usernametemp =redisTemplate.opsForValue().get("token_"+token).toString();
            }
            if(StringUtils.isBlank(usernametemp)){
                throw new UsernameNotFoundException("用户令牌过期");
            }
            request.getSession().setAttribute("token",token);
            UserInfoPojo userInfoPojo =userService.loginbyUsername(usernametemp);
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(usernametemp, userInfoPojo.getPassword());
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }

        //否则使用官方默认处理方式
        return super.attemptAuthentication(request, response);
    }
}

MyAuthenticationProvider:自定义的用户登录部分

/**
* 自定义校验-密码、图片验证码
*
*/
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
    //日志记录器
    private static Logger logger= LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);


    @Autowired
    private AuthUserDetailsService authUserDetailsService;
    @Autowired
    HttpServletRequest httpServletRequest;
    @Autowired
    PasswordEncoder passwordEncoder;


    /**
     * 登录验证码SessionKey
     */
    public static final String LOGIN_VALIDATE_CODE = "login_validate_code";


    /**
     * 自定义验证方式
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {


        String token =null;
        String password =null;
        UserDetails user =null;
        if(httpServletRequest.getSession().getAttribute("token")!=null){
            token = httpServletRequest.getSession().getAttribute("token").toString();
        }
        if(StringUtils.isBlank(token)){//使用密码进行登录  否则就是使用用户令牌进行登录
            String requestCode = httpServletRequest.getSession().getAttribute("vercode").toString();
            HttpSession session = httpServletRequest.getSession();
            String saveCode = httpServletRequest.getSession().getAttribute(LOGIN_VALIDATE_CODE).toString();
            //获取到session验证码后随时清除
            if(!StringUtils.isEmpty(saveCode)) {
                session.removeAttribute(LOGIN_VALIDATE_CODE);//captcha
                session.removeAttribute("vercode");
            }
            logger.info("requestCode:"+requestCode+",saveCode:"+saveCode);
            if(StringUtils.isEmpty(saveCode) || StringUtils.isEmpty(requestCode) || !requestCode.equals(saveCode)) {
                logger.info("图片验证码错误!");
                throw new DisabledException("图形验证码错误!");
            }
            logger.info("验证码验证成功");
            String username = authentication.getName();
             password = (String) authentication.getCredentials();
             user = authUserDetailsService.loadUserByUsername(username);
            //加密过程在这里体现
            logger.info("结果CustomUserDetailsService后,已经查询出来的数据库存储密码:" + user.getPassword());
            if (!passwordEncoder.matches(password, user.getPassword())) {
                logger.info("登录用户密码错误!");
                throw new DisabledException("登录用户密码错误!");
            }
        }else {//使用用户token进行登录
            String username = authentication.getName();
            password = (String) authentication.getCredentials();
             user = authUserDetailsService.loadUserByUsername(username);
        }
        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
        return new UsernamePasswordAuthenticationToken(user, password, authorities);
    }


    @Override
    public boolean supports(Class<?> arg0) {
        return true;
    }
}

KaptchaConfig:验证码配置类

/**
* 验证码配置类
*/
@Component
public class KaptchaConfig {


    @Bean
    public DefaultKaptcha getDefaultKaptcha() {
        com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
        Properties properties = new Properties();
        // 图片边框
        properties.setProperty("kaptcha.border", "no");
        // 边框颜色
        properties.setProperty("kaptcha.border.color", "black");
        //边框厚度
        properties.setProperty("kaptcha.border.thickness", "1");
        // 图片宽
        properties.setProperty("kaptcha.image.width", "200");
        // 图片高
        properties.setProperty("kaptcha.image.height", "50");
        //图片实现类
        properties.setProperty("kaptcha.producer.impl", "com.google.code.kaptcha.impl.DefaultKaptcha");
        //文本实现类
        properties.setProperty("kaptcha.textproducer.impl", "com.google.code.kaptcha.text.impl.DefaultTextCreator");
        //文本集合,验证码值从此集合中获取
        properties.setProperty("kaptcha.textproducer.char.string", "01234567890");
        //验证码长度
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        //字体
        properties.setProperty("kaptcha.textproducer.font.names", "宋体");
        //字体颜色
        properties.setProperty("kaptcha.textproducer.font.color", "black");
        //文字间隔
        properties.setProperty("kaptcha.textproducer.char.space", "5");
        //干扰实现类
        properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.DefaultNoise");
        //干扰颜色
        properties.setProperty("kaptcha.noise.color", "blue");
        //干扰图片样式
        properties.setProperty("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
        //背景实现类
        properties.setProperty("kaptcha.background.impl", "com.google.code.kaptcha.impl.DefaultBackground");
        //背景颜色渐变,结束颜色
        properties.setProperty("kaptcha.background.clear.to", "white");
        //文字渲染器
        properties.setProperty("kaptcha.word.impl", "com.google.code.kaptcha.text.impl.DefaultWordRenderer");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

生成验证码工具类

public class CommonUtil {
/**
* 生成验证码图片
* @param request 设置session
* @param response 转成图片
* @param captchaProducer 生成图片方法类
* @param validateSessionKey session名称
* @throws Exception
*/
public static void validateCode(HttpServletRequest request, HttpServletResponse response, DefaultKaptcha captchaProducer, String validateSessionKey) throws Exception{
    // Set to expire far in the past.
    response.setDateHeader("Expires", 0);
    // Set standard HTTP/1.1 no-cache headers.
    response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
    // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
    response.addHeader("Cache-Control", "post-check=0, pre-check=0");
    // Set standard HTTP/1.0 no-cache header.
    response.setHeader("Pragma", "no-cache");


    // return a jpeg
    response.setContentType("image/jpeg");


    // create the text for the image
    String capText = captchaProducer.createText();


    // store the text in the session
    request.getSession().setAttribute(validateSessionKey, capText);


    // create the image with the text
    BufferedImage bi = captchaProducer.createImage(capText);


    ServletOutputStream out = response.getOutputStream();


    // write the data out
    ImageIO.write(bi, "jpg", out);
    try {
        out.flush();
    } finally {
        out.close();
    }
}

}

Controller层验证码

@PostMapping(value = {"/loginValidateCode"})
@ApiOperation(value = "获取验证码图片")
public void loginValidateCode(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logger.info("start UserController.loginValidateCode");
    CommonUtil.validateCode(request, response, captchaProducer, LOGIN_VALIDATE_CODE);
    logger.info("end UserController.loginValidateCode");
}

验证码部分自测

在这里插入图片描述

响应结果的封装

MyAuthenctiationDeniedHandler:

/**
* 无权访问
*/
@Component
public class MyAuthenctiationDeniedHandler implements AccessDeniedHandler {
    private static Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);


    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException, ServletException {
        logger.info("无权访问!");


        ResponseBody responseBody = new ResponseBody();
        responseBody.setStatus("403");
        responseBody.setMsg("Need Authorities!");
        response.getWriter().write(JSON.toJSONString(responseBody));
    }
}

MyAuthenctiationEntryPointHandler

/**
* 未登录
*
*/
@Component
public class MyAuthenctiationEntryPointHandler implements AuthenticationEntryPoint{
    private static Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);


    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        logger.info("未登录!");


        ResponseBody responseBody = new ResponseBody();
        responseBody.setStatus("402");
        responseBody.setMsg("Need Login!");
        response.getWriter().write(JSON.toJSONString(responseBody));
    }
}

MyAuthenctiationFailureHandler

/**
* 登录失败
*
*/
@Component("myAuthenctiationFailureHandler")
public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    private static Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);


    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        logger.info("登录失败!");


        ResponseBody responseBody = new ResponseBody();
        responseBody.setStatus("401");
        responseBody.setMsg("Login Failure!");
        response.getWriter().write(JSON.toJSONString(responseBody));
    }
}

MyAuthenctiationInvalidSessionStrategy

/**
* session到期
*
*/
@Component
public class MyAuthenctiationInvalidSessionStrategy implements InvalidSessionStrategy{
    private static Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);


    @Override
    public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        logger.info("session到期!");


        ResponseBody responseBody = new ResponseBody();
        responseBody.setStatus("101");
        responseBody.setMsg("Session Expires!");
        response.getWriter().write(JSON.toJSONString(responseBody));

    }
}

MyAuthenctiationLogoutSuccessHandler

/**
* 退出登录
*
*/
@Component
public class MyAuthenctiationLogoutSuccessHandler implements LogoutSuccessHandler{


    @Autowired
    RedisTemplate redisTemplate;


    private static Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);


    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        logger.info("退出登录!");
        String username = authentication.getName();
        //删除用户令牌
        String token =redisTemplate.opsForValue().get("token_"+username).toString();
        redisTemplate.delete("token_"+username);
        redisTemplate.delete("token_"+token);
        request.getSession().setAttribute("token","");
        ResponseBody responseBody = new ResponseBody();
        responseBody.setStatus("200");
        responseBody.setMsg("Logout Success!");
        response.getWriter().write(JSON.toJSONString(responseBody));
    }
}

MyAuthenctiationSessionInformationExpiredStrategy

/**
* session到期,被登录
*/
@Component
public class MyAuthenctiationSessionInformationExpiredStrategy implements InvalidSessionStrategy {
    private static Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);


    @Override
    public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        logger.info("session到期,被登录!");


        ResponseBody responseBody = new ResponseBody();
        responseBody.setStatus("102");
        responseBody.setMsg("Session Expires!");
        response.getWriter().write(JSON.toJSONString(responseBody));
    }


}

MyAuthenctiationSuccessHandler

/**
* 登录成功
*
*/
@Component("myAuthenctiationSuccessHandler")
public class MyAuthenctiationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {


    @Autowired
    RedisTemplate redisTemplate;
    private static Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        logger.info("登录成功!");


        String token =null;
        if(request.getSession().getAttribute("token")!=null){
            token = request.getSession().getAttribute("token").toString();
        }
        if(StringUtils.isNotBlank(token)){
            //生成uuid作为用户token
            String tokentemp  = CommonUtil.getUUID();
            String username = authentication.getName();
            //将用户令牌存在redis中 生命周期为2个小时
            redisTemplate.opsForValue().set("token_"+username,tokentemp,7200, TimeUnit.SECONDS);
            redisTemplate.opsForValue().set("token_"+tokentemp,username,7200, TimeUnit.SECONDS);
            //将用户token放在cookie中
            CookieUtil.set(response, "token",tokentemp,7200);
        }
        ResponseBody responseBody = new ResponseBody();
        responseBody.setStatus("200");
        responseBody.setMsg("Login Success!");
        responseBody.setResult(token);
        response.getWriter().write(JSON.toJSONString(responseBody));
    }
}

验证

1.使用密码进行登录

在这里插入图片描述

2.使用token进行登录

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_40990818/article/details/106697125