构建项目
前提
会SpringBoot和tymeleaf
目的
了解SpringSecurity的helloworld
感悟
(1)核心就是继承WebSecurityConfigurerAdapter实现类里的configure(HttpSecurity http) 方法
(2)handler和Filter是加功能的关键
项目下载
https://github.com/cbeann/Demoo/tree/master/springsecuritydemo
初始化项目SpringBoot+web+security
pom
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>springsecuritydemo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springsecuritydemo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>
测试
@RequestMapping("/hello") @ResponseBody public String hello() { return "hello security"; }
项目启动后在浏览器输入 localhost:8080 (会默认有一个登陆页面)
用户名默认为 user
密码在控制台中
修改上面默认的用户名和密码
spring.security.user.name=root spring.security.user.password=123456
默认就是下面的配置
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin()//表单登陆 //http.httpBasic()//弹出框登陆 .and() .authorizeRequests()//下面是请求配置 .anyRequest()//任何请求 .authenticated();//都要认证 } }
完善案例之数据库数据登陆(假数据)(模仿)
创建User实体类
1)实现UserDetails接口,注意完善里面的方法
2)注意,每一个了方法的含义以及返回值
package com.example.springsecuritydemo.entity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * @author CBeann * @create 2019-08-16 10:22 */ public class User implements UserDetails { private String username; private String password; private List<String> permissions = new ArrayList<>(); /** * 是否被冻结、账号锁定等等标志位 */ private int sign = 1; @Override public String toString() { return "User{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", permissions=" + permissions + ", sign=" + sign + '}'; } public List<String> getPermissions() { return permissions; } public void setPermissions(List<String> permissions) { this.permissions = permissions; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public int getSign() { return sign; } public void setSign(int sign) { this.sign = sign; } public User(String username, String password) { this.username = username; this.password = password; } /** * 下边是接口的方法 */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { StringBuffer stringBuffer = new StringBuffer(""); for (String permission : permissions) { stringBuffer.append(permission + ","); } //permissions的格式为 damin,user,root(中间用逗号分开) String permissions = stringBuffer.substring(stringBuffer.length() - 1).toString(); return AuthorityUtils.commaSeparatedStringToAuthorityList(permissions); } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override //账号是否过期,true没有过期 public boolean isAccountNonExpired() { //你的业务逻辑 return true; } @Override //账号是否被锁定或者冻结,true为没有被冻结 public boolean isAccountNonLocked() { //你的业务逻辑 return true; } @Override //密码是否过期,true没有过期 public boolean isCredentialsNonExpired() { //你的业务逻辑 return true; } @Override //账号是否可用或者删除,true为没有被删除 public boolean isEnabled() { //你的业务逻辑 return true; } }
实现自定义密码加密器(本文没有做任何加密)
(1)实现PasswordEncoder接口,实现encode加密算法和macthes比较算法(此处没有加密,直接返回明文)
(2)将自定义加密算法注入到容器中
package com.example.springsecuritydemo.config; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author CBeann * @create 2019-08-16 15:57 */ public class MyPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence charSequence) {//加密算法,此处没有加密 String str = charSequence.toString(); return str; } @Override public boolean matches(CharSequence charSequence, String s) { String target = charSequence.toString(); if (target != null && s != null && target.equals(s)) { System.out.println("登陆成功"); return true; } System.out.println("登陆失败"); return false; } }
@Bean public PasswordEncoder passwordEncoder() { return new MyPasswordEncoder(); }
dao层(查询数据库)(此处用的静态数据)
package com.example.springsecuritydemo.dao; import com.example.springsecuritydemo.entity.User; import org.springframework.stereotype.Repository; import java.util.Arrays; import java.util.HashMap; import java.util.Map; /** * @author CBeann * @create 2019-08-16 10:21 */ @Repository public class UserDao { public static Map<String, User> users = new HashMap<>(); static { //初始化数据,假装自己查数据库 User user = new User("zhangsan", "123456"); user.setPermissions(Arrays.asList("admin", "user")); User user2 = new User("lisi", "123456"); user2.setPermissions(Arrays.asList("user")); users.put(user.getUsername(), user); users.put(user2.getUsername(), user2); } public User getUser(String username) { return users.getOrDefault(username,null); } }
编写登陆验证的类
(1)个人感觉类似Shiro里的自定义realm,这里自动实现授权和验证
(2)实现UserDetailsService接口里的loadUserByUsername方法,返回一个UserDetails的实现类,框架会自动调用UserDetails接口里的方法进行秘密校验,授权等等
(3)将该类注入到容器中
(4)此方法里返回一个UserDetail接口的对象,这就是为什么User实现UserDetail接口
package com.example.springsecuritydemo.service; import com.example.springsecuritydemo.dao.UserDao; import com.example.springsecuritydemo.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; /** * @author CBeann * @create 2019-08-16 10:20 */ @Component public class MyUserDetailService implements UserDetailsService { @Autowired private UserDao userDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { System.out.println("登陆用户:" + username); //查询数据库 User user = userDao.getUser(username); if(user==null){ throw new UsernameNotFoundException("账号不存在:UsernameNotFoundException"); } return user; } }
自定义登陆页面
加入tymeleaf依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
关闭缓存
spring.thymeleaf.cache=false
loginController
@Controller public class LoginController { @RequestMapping("/login") public String login(){ System.out.println("-----------login-------------"); return "login/mylogin"; } }
在templates下创建 login/mylogin.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登陆</title> </head> <body> <h1>登陆页面</h1> <form method="post" action="/authentication/form"> 用户名:<input type="text" name="username"/><br/> 密码:<input type="password" name="password"/><br/> <button type="submit">登陆</button> </form> </body> </html>
修改Security的配置类的configure方法
package com.example.springsecuritydemo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author CBeann * @create 2019-08-16 9:34 * SpringSecurty核心配置 */ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 配置密码加密器,可以自定义加密器,实现PasswordEncoder接口 */ @Bean public PasswordEncoder passwordEncoder() { return new MyPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { //表单登陆 http.formLogin() //http.httpBasic()//弹出框登陆 //告诉系统自动定登陆页面 .loginPage("/login") //告诉系统这个URL为登陆请求,系统会走登陆验证的过滤器 .loginProcessingUrl("/authentication/form") .and() .authorizeRequests()//下面是请求配置 .antMatchers("/login").permitAll()//当访问此URL(/login)时不需要验证 .anyRequest()//任何请求 .authenticated()//都要认证 .and().csrf().disable(); } }
就可以用自定义的页面进行登陆了,此时有一个问题,登陆失败怎么回显???就是下面的自定义成功、失败处理
自定义登陆成功处理和自定义登陆失败处理
自定义登陆成功处理(比如把user对象存到session中,发送登陆成功短息)
1)创建自定义登陆类
(2)继承SavedRequestAwareAuthenticationSuccessHandler,实现onAuthenticationSuccess方法
(3)在onAuthenticationSuccess方法中实现自己的成功登陆处理,比如用户加积分、登陆次数等等
(4)将类放在容器中
package com.example.springsecuritydemo.handler; import com.example.springsecuritydemo.entity.User; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author CBeann * @create 2019-08-16 21:23 */ @Component("myAuthenticationSuccessHandler") public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { System.out.println("登陆成功----------MyAuthenticationSuccessHandler"); System.out.println(authentication); //获取用户的信息 User loginUser = (User) authentication.getPrincipal(); //给用户法发个积分,做个记录等操作 //XXXService.method1() //调用框架原来的跳转 super.onAuthenticationSuccess(httpServletRequest, httpServletResponse, authentication); } }
自定义登陆失败处理(比如表单回显)
(1)创建自定义登陆类
(2)继承SimpleUrlAuthenticationFailureHandler,实现onAuthenticationFailure方法
(3)在onAuthenticationFailure方法中实现自己的失败处理,比如跳转到失败页面或者登陆页面
(4)将类放在容器中
package com.example.springsecuritydemo.handler; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author CBeann * @create 2019-08-17 9:32 */ @Component("myAuthenticationFailureHandler") public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { //不同的异常会有不同的信息,比如账号不存在、密码错误(坏的凭证)、账号不可用等等 String message = exception.getMessage(); //将错误信息回显到登陆页面 request.setAttribute("msg", message); request.getRequestDispatcher("/login").forward(request, response); // response.setContentType("application/json;charset=UTF-8"); // response.getWriter().write("登陆失败:"+message); } }
修改Security配置类(显式的设置登陆成功、失败的处理器)
package com.example.springsecuritydemo.config; import com.example.springsecuritydemo.service.MyUserDetailService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; /** * @author CBeann * @create 2019-08-16 9:34 * SpringSecurty核心配置 */ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailService myUserDetailService; /** * 解决UsernameNotFoundException不能被捕获的问题 */ @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setHideUserNotFoundExceptions(false); provider.setUserDetailsService(myUserDetailService); provider.setPasswordEncoder(passwordEncoder); return provider; } /** * 配置密码加密器,可以自定义加密器,实现PasswordEncoder接口 */ @Bean public PasswordEncoder passwordEncoder() { return new MyPasswordEncoder(); } @Autowired private PasswordEncoder passwordEncoder; @Autowired private AuthenticationSuccessHandler authenticationSuccessHandler; @Autowired private AuthenticationFailureHandler authenticationFailureHandler; @Override protected void configure(HttpSecurity http) throws Exception { //表单登陆 http.formLogin() //http.httpBasic()//弹出框登陆 //告诉系统自动定登陆页面 .loginPage("/login") //告诉系统这个URL为登陆请求,系统会走登陆验证的过滤器 .loginProcessingUrl("/authentication/form")//告诉系统登陆请求的url .successHandler(authenticationSuccessHandler)//自定义登陆成功处理 .failureHandler(authenticationFailureHandler)//自定义登陆失败处理 .and() .authorizeRequests()//下面是请求配置 .antMatchers("/login").permitAll()//当访问此URL(/login)时不需要验证 .anyRequest()//任何请求 .authenticated()//都要认证 .and().csrf().disable(); } }
登陆页面修改
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>登陆</title> </head> <body> <h1>登陆页面</h1> <span th:text="${msg}"></span> <form method="post" action="/authentication/form"> 用户名:<input type="text" name="username"/><br/> 密码:<input type="password" name="password"/><br/> <button type="submit">登陆</button> </form> </body> </html>
测试