1.springsecurity

springsecurity底层实现为一条过滤器链,就是用户请求进来,判断有没有请求的权限,抛出异常,重定向跳转。

2.登录页

springsecurity自带一个登录页。
从登陆入手,登录页替换成我们自己的,对输入的账号密码进行验证

/**
 * 表单登陆security
 * 安全  = 认证 + 授权
 */

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
            //以下五步是表单登录进行身份认证最简单的登陆环境
            http.formLogin() //表单登陆 1
                .and() //2
                .authorizeRequests() //下面的都是授权的配置 3
                .anyRequest() //任何请求 4
                .authenticated(); //访问任何资源都需要身份认证 5


    }
}

 

如果只实现一个WebSecurityConfigurerAdapter然后重写一下configure方法,效果会默认使用springsecurity的登录页 ,以及项目启动时后台会打印出一个默认的密码,然后使用任意账号就可以进行登录访问指定的资源


3.自定义登录页 与 UserDetailsService 用户名密码校验

如果想要使用自己的登录页 并且用户名密码是自己数据库中的,进一步完善spring security认证体系,首先需要做以下配置。

  @Override
    protected void configure(HttpSecurity http) throws Exception {
            //以下五步是表单登录进行身份认证最简单的登陆环境
            http.formLogin() //表单登陆 1
                    .loginPage("/login.html") //指定登陆页面
                .and() //2
                .authorizeRequests() //下面的都是授权的配置 3
                    .antMatchers("/login.html").permitAll()//访问此地址就不需要进行身份认证了,防止重定向死循环
                .anyRequest() //任何请求 4
                .authenticated(); //访问任何资源都需要身份认证 5
    }

然后实现UserDetailsService接口进行用户姓名密码校验 (由于springboot2.x中security是5.x版本的,所以这里的密码是默认做了BCrypt加密的,就需要bean一个BCrypt)

@Component
public class MyUserDetailService implements UserDetailsService {

    //注入mapper
    //...

    @Autowired
    private PasswordEncoder passwordEncoder;

    private Logger LOG = LoggerFactory.getLogger(MyUserDetailService.class);

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        LOG.error("登陆用户输入的用户名:{}",s);

        //根据用户名查找用户信息

        //密码进行bcrypt加密
        String pwd = "lisi123";
        //String cryptPwd = BCrypt.hashpw(pwd, BCrypt.gensalt());
        String cryptPwd = passwordEncoder.encode(pwd);

        LOG.error("加密后的密码为: {}",cryptPwd);

        return new User("s",cryptPwd, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); //账号 密码 权限
    }
}
/**
 * 表单登陆security
 * 安全  = 认证 + 授权
 */

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

      /**
     * 介绍
     *  springboot2.x引入的security版本是5.x的,这个版本需要提供一个PasswordEncoder实例,不然就会报错
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
            //以下五步是表单登录进行身份认证最简单的登陆环境
            http.formLogin() //表单登陆 1
                    .loginPage("/login.html") //指定登陆页面
                .and() //2
                .authorizeRequests() //下面的都是授权的配置 3
                    .antMatchers("/login.html").permitAll()//访问此地址就不需要进行身份认证了,防止重定向死循环
                .anyRequest() //任何请求 4
                .authenticated(); //访问任何资源都需要身份认证 5
    }
}

4.登陆页面提交页面 /authentication/form

添加登陆页面提交页面,关闭跨站请求伪造攻击,登陆访问资源

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陆</title>
</head>
<body>
   <h2>标准登陆页面</h2>
   <h3>表单登陆</h3>
   <form action = "/authentication/form" method ="post">
        <table>
            <tr>
                <td>用户名:</td>
                <td><input type="text" name="username"></td>
            </tr>
            <tr>
                <td>密码:</td>
                <td><input type="password" name="password"></td>
            </tr>
            <tr>
                <td colspan="2"><button type="submit">登陆</button></td>
            </tr>
        </table>
   </form>
</body>
</html>
@Override
    protected void configure(HttpSecurity http) throws Exception {
            //以下五步是表单登录进行身份认证最简单的登陆环境
            http.formLogin() //表单登陆 1
                .loginPage("/login.html") //指定登陆页面
                .loginProcessingUrl("/authentication/form")//登陆页面提交的页面 开始使用UsernamePasswordAuthenticationFilter过滤器处理请求
                .and() //2
                .authorizeRequests() //下面的都是授权的配置 3
                .antMatchers("/login.html").permitAll()//访问此地址就不需要进行身份认证了,防止重定向死循环
                .anyRequest() //任何请求 4
                .authenticated() //访问任何资源都需要身份认证 5
                .and()
                .csrf().disable();//关闭跨站请求伪造攻击拦截

    }

5.动态配置登录页

image.png

.做一个我们自己默认的登录页,如果不想用默认的也可以动态配置。使用到的注解@ConfigurationProperties。
.增加接口/authentication/require
.引导用户进入登录页登陆


 

   @Override
    protected void configure(HttpSecurity http) throws Exception {
            //以下五步是表单登录进行身份认证最简单的登陆环境
            http.formLogin() //表单登陆 1
                //.loginPage("/login.html") //指定登陆页面
                .loginPage("/authentication/require")
                .loginProcessingUrl("/authentication/form")//登陆页面提交的页面 开始使用UsernamePasswordAuthenticationFilter过滤器处理请求
                .and() //2
                .authorizeRequests() //下面的都是授权的配置 3
                .antMatchers("/login.html",
                        "/authentication/require",
                        securityProperties.getBrowser().getLoginPage()).permitAll()//访问此地址就不需要进行身份认证了,防止重定向死循环
                .anyRequest() //任何请求 4
                .authenticated() //访问任何资源都需要身份认证 5
                .and()
                .csrf().disable();//关闭跨站请求伪造攻击拦截

    }
@RestController
public class BrowserSecurityController {

    private Logger LOG  = LoggerFactory.getLogger(BrowserSecurityController.class);


    //将当前请求缓存到session里
    private RequestCache requestCache = new HttpSessionRequestCache();

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Resource
    private SecurityProperties securityProperties;

    /**
     * 当需要身份认证时跳转到这里
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "/authentication/require",method = RequestMethod.GET)
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED) //未授权状态码
    public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {

      //拿到引发跳转的请求
      SavedRequest savedRequest = requestCache.getRequest(request,response);

      if(savedRequest != null){
          String targetUrl = savedRequest.getRedirectUrl();
          String fileUrl=new URL(targetUrl).getFile();
          LOG.info("引发跳转的请求是:{}",targetUrl);

          if(StringUtils.endsWithIgnoreCase(targetUrl,".html") || fileUrl.equals("/")){
              //调转到登录页  》》这里登录页做成可配置的
              redirectStrategy.sendRedirect(request,response,securityProperties.getBrowser().getLoginPage());
          }
      }
      return new SimpleResponse("访问资源需要登陆,请访问登陆页面");
    }

}

从配置文件中读取当访问资源需要身份认证调转的页面地址

server.port=8888

#自定义springsecurity 登录页面
security.browser.loginPage = /mylogin.html
package com.example.security.properties;

import com.example.security.pojo.SecurityBrowserPojo;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 实现动态配置用户专属登陆页面
 */
@ConfigurationProperties(prefix = "security")
public class SecurityProperties {


    private SecurityBrowserPojo browser = new SecurityBrowserPojo();

    public SecurityBrowserPojo getBrowser() {
        return browser;
    }

    public void setBrowser(SecurityBrowserPojo browser) {
        this.browser = browser;
    }
}
public class SecurityBrowserPojo {

    //设置默认地址
    private String loginPage = "/login.html";

    public String getLoginPage() {
        return loginPage;
    }

    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }
}
package com.example.security.config.securityconfig;

import com.example.security.properties.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties({SecurityProperties.class}) //设置注解读取生效 (试了下不用配置这里@ConfigurationProperties也可以生效)
public class SecurityPropertiesConfig {
}

5.登陆成功/登陆失败处理

某些时候用户登陆成功,登陆失败的时候可能还需要做一些操作,比如成功登陆增加一积分之类的操作,这里需要做两个handler处理器

/**
 * 设置通过请求拦截。登陆成功后处理
 */
@Component("wawAuthenticationSuccessHandler")
public class WawAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private Logger LOG = LoggerFactory.getLogger(WawAuthenticationSuccessHandler.class);

    @Resource
    private ObjectMapper objectMapper;

    /**
     * @param authentication   封装认证信息>>用户信息 请求ip之类的
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        LOG.info("登陆成功");

        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(authentication));
    }
}

/**
 * 设置通过请求拦截。登陆失败后处理
 */
@Component("wawAuthenticationFailHandler")
public class WawAuthenticationFailHandler implements AuthenticationFailureHandler{

    private Logger LOG = LoggerFactory.getLogger(WawAuthenticationFailHandler.class);

    @Resource
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        LOG.info("登陆失败");

        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(e));



    }
}

成功与失败的处理器 配置到配置信息中


    @Override
    protected void configure(HttpSecurity http) throws Exception {
            //以下五步是表单登录进行身份认证最简单的登陆环境
            http.formLogin() //表单登陆 1
                //.loginPage("/login.html") //指定登陆页面
                .loginPage("/authentication/require")
                .loginProcessingUrl("/authentication/form")//登陆页面提交的页面 开始使用UsernamePasswordAuthenticationFilter过滤器处理请求
                .successHandler(wawAuthenticationSuccessHandler)
                .failureHandler(wawAuthenticationFailHandler)
                .and() //2
                .authorizeRequests() //下面的都是授权的配置 3
                .antMatchers("/authentication/require",
                        "/login.html",
                        securityProperties.getBrowser().getLoginPage()).permitAll()//访问此地址就不需要进行身份认证了,防止重定向死循环
                .anyRequest() //任何请求 4
                .authenticated() //访问任何资源都需要身份认证 5
                .and()
                .csrf().disable();//关闭跨站请求伪造攻击拦截

    }

登陆失败就会返回500 登陆异常信息

 

 

 

 

 

 

 

 

Logo

Authing 是一款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务

更多推荐