spring security 学习
一、Spring Security简介
Spring Security 是 Spring 家族中的一个安全管理框架,主要用于 Spring 项目组中提供安全认证服务,该框架主要的核心功能有认证、授权和攻击防护。
二、Spring Security入门
(1)Spring Security引入
创建一个最简单的Spring Security工程。引入Spring Web,Spring Securi
创建一个Controller包,并写一个LoginController方法
@Controller
public class LoginController {
@RequestMapping("login")
public String login(){
System.out.println("执行登录方法");
return "redirect: main.html";
}
}
先创建两个html,login.html和main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username"/><br/>
密 码:<input type="password" name="password"/><br/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>main</title>
</head>
<body>
登录成功
</body>
</html>
运行并访问。因为我们还没有修改配置,我们自己写的登录页面还没有起作用。现在我们可以看到Spring Security自带的登录页面。系统默认的username是user。密码会自动在控制台中打印输出。
(2)自定义登录
在写登录代码之前我们需要了解Spring Security提供的两个接口。
UserDetailsService
在实际开发中我们不会用Spring Security提供的账号密码,我们都是从数据库中获取的。
我们需要编写一个类来实现UserDetailsService,编写查询数据过程。返回User对象,这个User对象是安全框架自己提供的对象。
PassWordEncoder
对密码加密
创建一个service包,在包下创建一个UserDetailsServiceImpl并实现UserDetailsService
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder pw;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.查询数据库判断用户是否存在,如果不存在就会抛出usernameNotFoundException异常。我们先自定义一个admin的用户
if(!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在");
}
String password=pw.encode("123");
//返回User对象,这个User对象是安全框架自己提供的对象。
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
}
}
现在在使用之前的username就会报错
(3)自定义登录页面
在实际开发往往我们也会自定义登录页面,这样我们实现很精美的登录页面。
我们创建一个config包,再创建一个SecurityConfig类,让它继承WebSecurityConfigurerAdapter。再重写configure(HttpSecurity http)方法。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义表单提交
http.formLogin()
.loginPage("/login.html");
}
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
}
我们再次登录login.html,它会直接来到我们自定义页面。
但是我们尝试登录main.html系统也能登录。所以之前系统自带的认证方法已经失效了。
现在我们就需要自己编写认证。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义表单提交
http.formLogin()
.loginPage("/login.html");
//授权认证
http.authorizeRequests()
//login.html不需要认证
.mvcMatchers("/login.html").permitAll()
//所有请求都必须被认证,这样只有登录后才能访问
.anyRequest().authenticated();
}
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
}
再次运行,我们先使用main.html,发现现在系统会自动跳转登录页面。
但是,我们用之前的username和password是无法登录页面的。
我们需要先关闭csrf防护,同时设置跳转页面,并编写UserDetailsServiceImpl拦截。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义表单提交
http.formLogin()
//当发现/login时认为时登录,必须和表单提交的地址一样,这样才会去执行UserDetailsServiceImpl
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/login.html")
.successForwardUrl("/main.html");
//授权认证
http.authorizeRequests()
//login.html不需要认证
.mvcMatchers("/login.html").permitAll()
//所有请求都必须被认证,这样只有登录后才能访问
.anyRequest().authenticated();
//关闭csrf防护
http.csrf().disable();
}
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
}
但是现在会报错。会提示请求方式错误。因为登录成功后跳转页面必须时post请求。
所以我们必须修改跳转方法。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义表单提交
http.formLogin()
//当发现/login时认为时登录,必须和表单提交的地址一样,这样才会去执行UserDetailsServiceImpl
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/login.html")
//登录成功后跳转页面必须是post请求
.successForwardUrl("/toMain");
//授权认证
http.authorizeRequests()
//login.html不需要认证
.mvcMatchers("/login.html").permitAll()
//所有请求都必须被认证,这样只有登录后才能访问
.anyRequest().authenticated();
//关闭csrf防护
http.csrf().disable();
}
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
}
并在LoginController中添加一个toMain方法
/**
* 页面跳转
* @return
*/
@RequestMapping("toMain")
public String toMian(){
return "redirect: main.html";
}
(4)自定义失败页面
我们先创建一个error页面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Error</title>
</head>
<body>
操作失败,请重新登录<a href="/login.html">跳转</a>
</body>
</html>
在SecurityConfig中编写登录错误时的跳转方法并打开error.html的认证。同时在LoginController中添加一个toError方法
//自定义表单提交
http.formLogin()
//当发现/login时认为时登录,必须和表单提交的地址一样,这样才会去执行UserDetailsServiceImpl
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/login.html")
//登录成功后跳转页面必须是post请求
.successForwardUrl("/toMain")
//跳转失败页面也是post请求
.failureForwardUrl("/toError");
//授权认证
http.authorizeRequests()
//error.html不需要认证
.mvcMatchers("/error.html").permitAll()
//login.html不需要认证
.mvcMatchers("/login.html").permitAll()
//所有请求都必须被认证,这样只有登录后才能访问
.anyRequest().authenticated();
/**
* 错误跳转
* @return
*/
@RequestMapping("toError")
public String toError(){
return "redirect: error.html";
}
(5)自定义登录成功和失败处理器
创建一个handle包,再创建一个MyAuthenticationSuccessHandler类并实现AuthenticationSuccessHandler
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
public MyAuthenticationSuccessHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(url);
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.sendRedirect(url);
}
}
在SecurityConfig中添加successHandler处理器
//自定义表单提交
http.formLogin()
//当发现/login时认为时登录,必须和表单提交的地址一样,这样才会去执行UserDetailsServiceImpl
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/login.html")
//登录成功后跳转页面必须是post请求
// .successForwardUrl("/toMain")
//登录成功后的处理器不能和successForwardUrl共存
.successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"))
//跳转失败页面也是post请求
.failureForwardUrl("/toError");
与成功处理器相同,我们创建一个MyAuthenticationFHandler类并实现AuthenticationFailureHandler
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String url;
public MyAuthenticationFailureHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.sendRedirect(url);
}
}
在SecurityConfig中添加failureHandler处理器
//自定义表单提交
http.formLogin()
//当发现/login时认为时登录,必须和表单提交的地址一样,这样才会去执行UserDetailsServiceImpl
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/login.html")
//登录成功后跳转页面必须是post请求
// .successForwardUrl("/toMain")
//登录成功后的处理器不能和successForwardUrl共存
.successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"))
//跳转失败页面也是post请求
// .failureForwardUrl("/toError");
.failureHandler(new MyAuthenticationFailureHandler("/error.html"));
这样可以直接跳转error.html。
(6)权限判断
我们先创建一个页面main1.html,让拥有admin权限的用户才能看到它。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Main1</title>
</head>
<body>
admin用户才能打开
</body>
</html>
在admin.html中添加一个a标签,跳转main1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>main</title>
</head