spring security 开发 基于表单的认证

终于进入 spring security 的章节了


在这里插入图片描述

http basic 安全验证

在第一章构建项目中说到
当项目依赖中 含有 spring security 的jar 包时
它会有一个默认的安全配置http basic
所有一开始我们用下面的注解移除掉了它的默认安全配置

在这里插入图片描述
spring security 默认的安全验证我们一般是不用的

实现用户名+密码认证

开始开发基于表单的安全验证

创建 BrowserSecurityConfig 类 配置安全验证

在这里插入图片描述

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()   //指定身份认证的方式为表单登录
                .and()
                .authorizeRequests() //对请求授权
                .anyRequest()        //任何请求
                .authenticated();    //安全认证
        //任何请求都必须经过表单验证才能进行访问

    }
}

运行并测试

直接运行我们这个项目 demoApplication ,发现报错

No qualifying bean of type 'org.springframework.security.config.annotation.ObjectPostProcessor<?>' available
2019-04-06 19:31:05.208  INFO 18932 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]

https://stackoverflow.com/questions/32081578/spring-security-nosuchbeandefinitionexception-no-qualifying-bean-of-type-org
原因是我们关闭了spring security的安全验证配置
把红框里的注解去掉
在这里插入图片描述

再次运行 成功
访问 http://localhost:8080/hello 后会跳到登录界面

用户名为 user
密码 在 控制台打印出了,这个密码每次启动都会变
在这里插入图片描述
当登录成功后,它又会跳到我们之前访问的url

如果还想用http basic 验证 ,只需更改一行代码
在这里插入图片描述
这样就可以了

基本原理

主要是由一层层过滤器构成····
在这里插入图片描述

自定义用户认证逻辑

处理用户信息获取逻辑

用户信息的获取 在 spring security 中是被 封装在 userDetailService 接口中
在这里插入图片描述
自定义自己的userDetailService 实现类
在这里插入图片描述

@Component
public class MyUserDetailsService implements UserDetailsService {
    // 这里注入dao 层 用于查询数据库

    Logger logger = LoggerFactory.getLogger(getClass());

    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        //根据用户名从数据库查找用户信息

        logger.info("登录用户名"+ userName);
        //我们要验证用户名 密码 并 查取权限
        //这个user是spring中的类,它实现了UserDetails 接口
        User admin = new User(userName, "123456",
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));//第三个参数是做授权的
        return admin;
    }
}

启动应用 访问 http://localhost:8080/hello 输入用户密码后台报错
在这里插入图片描述
原因:版本升级 因为Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released
https://docs.spring.io/spring-security/site/docs/5.0.0.RELEASE/reference/htmlsingle/#ns-password-encoder
https://www.cnblogs.com/majianming/p/7923604.html
https://blog.csdn.net/Canon_in_D_Major/article/details/79675033
解决:待会解决 需要配置 PasswordEncoder

处理用户校验逻辑

用户是否冻结,密码是否过期等验证信息
在这里插入图片描述

处理密码加密解密逻辑

配置 PasswordEncoder

在这里插入图片描述
修改 MyUserDetailsService 的 loadUserByUsername 的方法
在这里插入图片描述
在这里插入图片描述

测试成功 ,也解决了之前的问题
在这里插入图片描述

注意 密码都是123456 ,但每次打印出来得密码都不一样 不像md5

因为他会随机生成一个盐,将随机生成的盐混在密码串里面,当他判断的时候 再用随机生成的盐来反推当时的密码串

一些问题restful返回及登录路径可配置

在这里插入图片描述
1、我们前面说过restful返回的应该是状态码和json信息,但现在认证成功后返回的是一个html页面
解决思路:如果是html请求就跳转到登录页面上,如果不是就返回json
2、代码重构,使其可以自定义配置登录页面
在这里插入图片描述
在这里插入图片描述

处理不同类型的请求

在这里插入图片描述

@RestController
public class BrowserSecurityController {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private RequestCache requestCache = new HttpSessionRequestCache();//缓存到session中的请求

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();//请求跳转工具

    @Autowired
    private SecurityProperties securityProperties;

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

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

        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            logger.info("引发跳转的请求是:" + targetUrl);
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
                redirectStrategy.sendRedirect(request, response, securityProperties.getBrowser().getLoginPage());// securityProperties.getBrowser().getLoginPage()配置登录页面
            }
        }

        return new SimpleResponse("访问的服务需要身份认证,请引导用户到登录页");
    }


}

SimpleResponse返回类型

在这里插入图片描述

public class SimpleResponse {
	
	public SimpleResponse(Object content){
		this.content = content;
	}
	
	private Object content;//Object可以返回任何对象

	public Object getContent() {
		return content;
	}

	public void setContent(Object content) {
		this.content = content;
	}
	
}

可配置的登录页面

在这里插入图片描述

SecurityProperties

@ConfigurationProperties(prefix = "whale.security") //这个类会读取以whale.security开头的配置项
public class SecurityProperties {
	//浏览器配置
	private BrowserProperties browser = new BrowserProperties();

	public BrowserProperties getBrowser() {
		return browser;
	}

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

BrowserProperties 浏览器配置属性

public class  BrowserProperties {

	private String loginPage = "/signIn.html";

	public String getLoginPage() {
		return loginPage;
	}

	public void setLoginPage(String loginPage) {
		this.loginPage = loginPage;
	}
}

get set 属性方法要一致否则报错,无法启动

 ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'browserSecurityConfig': Unsatisfied dependency expressed through field 'securityProperties'; nested exception is org.springframework.boot.context.properties.ConfigurationPropertiesBindException: Error creating bean with name 'whale.security-com.whale.security.core.properties.SecurityProperties': Could not bind properties to 'SecurityProperties' : prefix=whale.security, ignoreInvalidFields=false, ignoreUnknownFields=true; nested exception is org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'whale.security.browser' to com.whale.security.core.properties.BrowserProperties

最后要使配置生效SecurityCoreConfig

@Configuration
@EnableConfigurationProperties(SecurityProperties.class)//使SecurityProperties中的配置生效
public class SecurityCoreConfig {

}

demo中自定义登录页面

在这里插入图片描述

#配置登录页面
whale.security.browser.loginPage = /demo-signIn.html

BrowserSecurityConfig 使用配置类

在这里插入图片描述

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationSuccessHandler whaleAuthenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler whaleAuthenctiationFailureHandler;

    @Autowired
    private SecurityProperties securityProperties;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();//也可以自定义 只要实现PasswordEncoder接口
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //http.formLogin()   //指定身份认证的方式为表单登录
        //http.httpBasic()

        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setAuthenticationFailureHandler(whaleAuthenctiationFailureHandler);//设置错误过滤器

        http.addFilterBefore(validateCodeFilter,UsernamePasswordAuthenticationFilter.class)
                .formLogin()
//                .loginPage("/signIn.html") //指定登录页面的url
//                .loginPage("/anthentication/require") //指定登录页面的url
                .loginPage(securityProperties.getBrowser().getLoginPage()) //指定登录页面的url
                .loginProcessingUrl("/authentication/form")
                .successHandler(whaleAuthenticationSuccessHandler)
                .failureHandler(whaleAuthenctiationFailureHandler)
                .permitAll()
                .and()
                .authorizeRequests() //对请求授权
//                .antMatchers("/signIn.html","/code/image").permitAll() //加一个匹配器 对匹配的路径不进行身份认证
                .antMatchers(securityProperties.getBrowser().getLoginPage(),"/code/image").permitAll() //加一个匹配器 对匹配的路径不进行身份认证
                .anyRequest()        //任何请求
                .authenticated()    //安全认证
                .and()
                .cors().disable().csrf().disable();// 禁用跨站攻击
                // 默认都会产生一个hiden标签 里面有安全相关的验证 防止请求伪造 这边我们暂时不需要 可禁用掉
                //任何请求都必须经过表单验证才能进行访问

       /* http.csrf().disable().cors().disable().headers().disable()
                .authorizeRequests()
                .antMatchers("/signIn.html").permitAll() // 配置不需要身份认证的请求地址
                .anyRequest().authenticated() // 其他所有访问路径需要身份认证
                .and()
                .formLogin()
                .loginPage("/signIn.html") // 指定登录请求地址
                .loginProcessingUrl("/authentication/form")
                .permitAll();
        */


    }
}

测试

在这里插入图片描述

个性化用户认证流程

自定义登录页面

定义登录页面 ,并配置 以及 可能出现的错误原因
在这里插入图片描述

<html>
<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>

配置登录页面 即 会有的错误
在这里插入图片描述
解决
在这里插入图片描述
启动测试 成功
在这里插入图片描述

自定义登录处理

在这里插入图片描述
在这里插入图片描述
具体要求和解释如下
https://docs.spring.io/spring-security/site/docs/5.2.0.BUILD-SNAPSHOT/api/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.html

https://docs.spring.io/spring-security/site/docs/5.2.0.BUILD-SNAPSHOT/reference/htmlsingle/#jc-form

禁用跨站攻击

但我不知道什么原因我的 loginProcessingUrl 设置一直失效,查了好久也没解决,后面有时间再看

原因:spring security 提供了csrf跨站保护,需要把他关掉
我看了别人的文章,只写csrf().disable(); 是关不掉的,必须这样写 .cors().disable().csrf().disable();// 禁用跨站攻击
完整代码如下:

protected void configure(HttpSecurity http) throws Exception {
        //http.formLogin()   //指定身份认证的方式为表单登录
        //http.httpBasic()
        http.formLogin()
                .loginPage("/signIn.html") //指定登录页面的url
                .loginProcessingUrl("/authentication/form")
                .permitAll()
                .and()
                .authorizeRequests() //对请求授权
                .antMatchers("/signIn.html").permitAll() //加一个匹配器 对匹配的路径不进行身份认证
                .anyRequest()        //任何请求
                .authenticated()    //安全认证
                .and()
                .cors().disable().csrf().disable();// 禁用跨站攻击
               // .headers().disable();
                // 默认都会产生一个hiden标签 里面有安全相关的验证 防止请求伪造 这边我们暂时不需要 可禁用掉
                //任何请求都必须经过表单验证才能进行访问

       /* http.csrf().disable().cors().disable().headers().disable()
                .authorizeRequests()
                .antMatchers("/signIn.html").permitAll() // 配置不需要身份认证的请求地址
                .anyRequest().authenticated() // 其他所有访问路径需要身份认证
                .and()
                .formLogin()
                .loginPage("/signIn.html") // 指定登录请求地址
                .loginProcessingUrl("/authentication/form")
                .permitAll();
     
        */


    }

自定义登录成功处理

创建一个类实现 AuthenticationSuccessHandler 接口

在这里插入图片描述

@Component("whaleAuthenticationSuccessHandler")
public class WhaleAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

	private Logger logger = LoggerFactory.getLogger(getClass());

	// ObjectMapper spring mvc 提供的
	@Autowired
	private ObjectMapper objectMapper;


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.security.web.authentication.
	 * AuthenticationSuccessHandler#onAuthenticationSuccess(javax.servlet.http.
	 * HttpServletRequest, javax.servlet.http.HttpServletResponse,
	 * org.springframework.security.core.Authentication)
	 */
	@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));

	}
}

配置successHandle查看authentication信息

在这里插入图片描述
然后启动访问 http://localhost:8080/hello
登录跳转到 http://localhost:8080/authentication/form
页面如下

{
	"authorities": [{
		"authority": "admin"
	}],
	"details": {
		"remoteAddress": "0:0:0:0:0:0:0:1",
		"sessionId": "49325D5A6ADCA79A3D53D3B823A32B41"
	},
	"authenticated": true,
	"principal": {
		"password": null,
		"username": "admin",
		"authorities": [{
			"authority": "admin"
		}],
		"accountNonExpired": true,
		"accountNonLocked": true,
		"credentialsNonExpired": true,
		"enabled": true
	},
	"credentials": null,
	"name": "admin"
}

自定义失败处理

同自定义成功处理一样
它要实现 AuthenticationFailureHandler接口
在这里插入图片描述

@Component("whaleAuthenctiationFailureHandler")
public class WhaleAuthenctiationFailureHandler implements AuthenticationFailureHandler {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	private ObjectMapper objectMapper;

	public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException ) throws IOException, ServletException {
		logger.info("登录失败");
		httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
		httpServletResponse.setContentType("application/json;charset=UTF-8");
		httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authenticationException));
	}

}

在这里插入图片描述
登录失败后
跳转到 http://localhost:8080/authentication/form

在这里插入图片描述
它也可以直接继承SimpleUrlAuthenticationFailureHandler 它是接口的默认实现

成功与失败处理器重构为可配置

定于返回类型枚举

在这里插入图片描述

public enum LoginType {
    /**
     * 跳转
     */
    REDIRECT,

    /**
     * 返回json
     */
    JSON
}

public class  BrowserProperties {

	private String loginPage = "/signIn.html";

	private LoginType loginType = LoginType.JSON;
	·············

成功和失败处理改造

在这里插入图片描述

//implements AuthenticationFailureHandler
@Component("whaleAuthenctiationFailureHandler")
public class WhaleAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	private ObjectMapper objectMapper;

	@Autowired
	private SecurityProperties securityProperties;

	@Override
	public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException ) throws IOException, ServletException {
		logger.info("登录失败");
		if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
			httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
			httpServletResponse.setContentType("application/json;charset=UTF-8");
			httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authenticationException));
		}else {
			super.onAuthenticationFailure(httpServletRequest,httpServletResponse,authenticationException);
		}

	}

}
//implements AuthenticationSuccessHandler
@Component("whaleAuthenticationSuccessHandler")
public class WhaleAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

	private Logger logger = LoggerFactory.getLogger(getClass());

	// ObjectMapper spring mvc 提供的
	@Autowired
	private ObjectMapper objectMapper;

	@Autowired
	private SecurityProperties securityProperties;


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.security.web.authentication.
	 * AuthenticationSuccessHandler#onAuthenticationSuccess(javax.servlet.http.
	 * HttpServletRequest, javax.servlet.http.HttpServletResponse,
	 * org.springframework.security.core.Authentication)
	 */
	@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 {
			//如果配置的返回类型不是json,则调用父类方法,进行跳转
			super.onAuthenticationSuccess(request,response,authentication);
		}


	}
}

测试

demo中配置登录类型

whale.security.browser.loginType = REDIRECT

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值