<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
还有JDBC、web、mysql、lombok就不写了
一、认证和授权
我这刚学到security框架,就只用到一个配置类而已,在里面写东西。
1、一个普通的myLogin.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="login"></div>
<h2>登陆</h2>
<!-- 这里写空或者myLogin.html都行-->
<form action="myLogin.html" method="post">
<input type="text" name="username" placeholder="username">
<input type="password" name="password" placeholder="password">
<input type="submit" value="login">
</form>
</body>
</html>
2、一个普通的yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
3、一个普通的controller
@RestController
public class MainController {
@GetMapping("/")
public String hello(){
System.out.println("hello");
return "hello";
}
@GetMapping("/test")
public String test(){
return "test";
}
@GetMapping("/admin")
public String admin(){
return "hello admin";
}
@GetMapping("/user")
public String user(){
return "hello user";
}
@GetMapping("app")
public String app(){
return "hello app";
}
}
4、普通的2个表
users:
authorities:
5、一个不普通的security配置类
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//一堆代码
}
这个配置类呢,就继承WebSecurityConfigurerAdapter
,里面有好多方法,但主要的有设置用户的configure(AuthenticationManagerBuilder auth)
、设置拦截器的configure(HttpSecurity http)
、用户持久层的userDetailsService()
1)configure(AuthenticationManagerBuilder auth)
在这个函数里面设置用户什么的,这里还只是放在存储中的,实际要放在数据库中的,后面演示
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
// 在内存中设置用户,可以用账号登陆
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 安全框架多少以后,要设置加密模式了
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user").password(new BCryptPasswordEncoder().encode("123")).roles("USER")
.and()
.withUser("admin").password(new BCryptPasswordEncoder().encode("123")).roles("USER","ADMIN");
}
- 这个,就在内存中设置一个用户了,用and()连接,密码一定要加密的,后面可以加个权限角色
2)configure(HttpSecurity http)
设置各种拦截的东西,哪些请求需要登陆,哪些不需要登陆
// 设置安全拦截设置的地方
@Override
protected void configure(HttpSecurity http) throws Exception {
// http.authorizeRequests().anyRequest().authenticated() 返回一个拦截注册器
//authorizeRequests(),用安全框架的开端
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/app/**").permitAll()///app/**所有人都可以访问,不受安全框架拦截
.anyRequest().authenticated() //任何的请求,认证后才能访问
.and()
// 登陆页面为 /myLogin.html,并且登陆页不设权限
.formLogin().loginPage("/myLogin.html") //登陆的页面为
// .loginProcessingUrl("/test")//指定处理登陆请求的路径,但是俺不懂,但是现在懂了!
/*这个是登陆成功的逻辑
*/
.successHandler(new AuthenticationSuccessHandler(){
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("{\"error_code\":\"0\",\"message\":\"欢迎登陆系统\"}");
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("{\"error_code\":\"401\",\"name\":\"" +e.getClass() + "\",\"message\":\"" + e.getMessage() + "\"}");
}
})
.permitAll()
.and()
// csrf是跨域保护功能
.csrf().disable();
}
authorizeRequests()
:用安全框架的开端,必写.antMatchers("/admin/**").hasRole("ADMIN")
:访问http://localhost:8080/admin与admin之下的请求,都要拥有ADMIN
权限才能访问,没有权限返回登陆页面.antMatchers("/app/**").permitAll()
:人人都能访问app下的请求.anyRequest().authenticated()
:任何的请求,认证后才能访问.formLogin().loginPage("/myLogin.html")
登陆的页面为什么,放在resources/static下.successHandler().failureHandler.permitAll()
,前者是登陆成功之后的处理逻辑,后者是登陆失败的处理逻辑。然后这里有个permitAll()
我也不知道为什么.csrf().disable();
使csrf跨域保护失效,固定写法。csrf请求:除了内部请求之外的请求,都是攻击请求。但是一般会有多端口请求,所以security设置的安全有点太高了,降下来loginProcessingUrl()
:反正就记得,这个参数的值和form表单的action值一样就行,很重要!!!!!!
3) userDetailsService()
这个是用于连接数据库持久层的,为数据库添加东西
// 在表中创建几个用户,链接数据库,在数据表中创建数据,但是不能登陆直接来用
@Override
protected UserDetailsService userDetailsService() {
JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
manager.setDataSource(dataSource);
if (!manager.userExists("user")){
manager.createUser(User.withUsername("user").password("123").roles("USER").build());
}
if (!manager.userExists("admin")){
manager.createUser(User.withUsername("admin").password("123").roles("ADMIN").build());
}
return manager;
}
这里运行后,在表中自动生成数据
6、演示
登陆成功
用user进行登陆
这里会权限不足,因为user用户没有权利访问admin请求
至今为止的代码 https://github.com/E-10000/spring-security-demo/tree/master
二、自定义认证和授权(有待考究,还不会连接数据库)
这里就另起炉灶了,新创项目
其实这个东西干嘛的我也不太懂。。。
1、一个表
2、实体层
@Data
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private String roles;
private boolean enable;
private List<GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enable;
}
}
3、准备好持久层(有BUG,在本文最后面有说要怎么做)
1)
安装mybatis,
spring:
datasource:
url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
2)UserMapper :
@Mapper
public interface UserMapper {
@Select("select * from users where username=#{username}")
User findByUsername(@Param("username") String username);
}
4、编写MyUserDetailsService
//不知道为什么加载不了
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 从数据库读取用户
User user = userMapper.findByUsername(s);
//用户不存在时抛出异常
if (user == null){
throw new UsernameNotFoundException("用户不存在");
}
//将数据库形式的roles解析为UserDetails的权限
user.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles()));
return user;
}
//实行权限的转换
private List<GrantedAuthority> generateAuthorities(String roles){
List<GrantedAuthority> authorities = new ArrayList<>();
String[] roleArray = roles.split(";");
if (roles !=null && !"".equals(roles)){
for (String role:roleArray) {
authorities.add(new SimpleGrantedAuthority(role));
}
}
return authorities;
}
}
这里代码写着是从数据库中读取用户,但老子实际上用的时候也没见到他用到。。。
三、实现图形验证码
众所周知,登陆乃是要验证码的,用验证码登陆,然后将这个系统加入到过滤器中,这样春鞋就自动完成了,真实不可思议呢
1、导包
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
2、图片验证码配置类
/*
配置类,用于配置一个验证图片
*/
@Configuration
public class KaptchaConfig {
@Bean
public Producer captcha(){
Properties properties = new Properties();
//宽度
properties.setProperty("kaptcha.image.width","150");
//长度
properties.setProperty("kaptcha.image.height","50");
//字符集,0-9
properties.setProperty("kaptcha.textproducer.char.string","0123456789");
//字符长度,4个长度
properties.setProperty("kaptcha.textproducer.char.length","4");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha =new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
3、图片控制类
用于获取图片验证码
/*
当访问/captcha.jpg,得到一张基于配置类设定的一张图片,验证码保存与哦session中
*/
@RestController
public class CaptchaController {
@Autowired
private Producer captchaProducer;
@GetMapping("/captcha.jpg")
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
//设置内容类型
response.setContentType("image/jpeg");
//创建验证码文本
String capText = captchaProducer.createText();
//将验证码文本设置到session
request.getSession().setAttribute("captcha",capText);
//创建验证码图片
BufferedImage bi = captchaProducer.createImage(capText);
//获取相应输出流
ServletOutputStream out = response.getOutputStream();
//将图片验证码写到响应输出流
ImageIO.write(bi,"jpg",out);
//推送并且关闭输出流
try{
out.flush();
}finally {
out.close();
}
}
}
可以直接访问啦
4、定义验证码失败的异常
public class VerificationCodeException extends AuthenticationException {
public VerificationCodeException() {
super("图形验证码校验失败");
}
}
5、验证码过滤器
//检验验证码的过滤器
public class VerificationCodeFilter extends OncePerRequestFilter {
private AuthenticationFailureHandler authenticationFailureHandler = new MyAuthenticationFailureHandler();
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//非登陆请求不校验验证码
if (!"/auth/form".equals(httpServletRequest.getRequestURI())){
filterChain.doFilter(httpServletRequest,httpServletResponse);
}else {
try {
verificationCode(httpServletRequest);
filterChain.doFilter(httpServletRequest,httpServletResponse);
}catch (VerificationCodeException e){
authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
}
}
}
public void verificationCode(HttpServletRequest httpServletRequest) throws VerificationCodeException {
String requestCode = httpServletRequest.getParameter("captcha");
HttpSession session = httpServletRequest.getSession();
String saveCode = (String) session.getAttribute("captcha");
if (!StringUtils.isEmpty(saveCode)){
//随手清楚验证码,无论成功失败,客户端应该在登陆失败时刷新验证码
session.removeAttribute("captcha");
}
//验证不通过
if (StringUtils.isEmpty(requestCode)||StringUtils.isEmpty(saveCode)||!requestCode.equals(saveCode)){
throw new VerificationCodeException();
}
}
}
AuthenticationFailureHandler
这个类其实无所谓啦,代码为
作用是显示错误而已啦public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=UTF-8"); PrintWriter out = httpServletResponse.getWriter(); out.write("{\"error_code\":\"401\",\"name\":\"" +e.getClass() + "\",\"message\":\"" + e.getMessage() + "\"}"); } }
- 第八行那个,一定要写清楚啊,
/auth/form
是输完账号密码之后,要跳转到的请求(这个可以通过loginProcessingUrl()
设置,也是这个函数的参数),只有那个页面才检索验证码是否正确啦,其他的地方都不用检索验证码
6、安全框架配置也要跟着修改
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
// 在内存中设置用户,可以用账号登陆
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 安全框架多少以后,要设置加密模式了
auth
.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user").password(new BCryptPasswordEncoder().encode("123")).roles("USER")
.and()
.withUser("admin").password(new BCryptPasswordEncoder().encode("123")).roles("USER","ADMIN");
}
@Override
public void configure(WebSecurity web) throws Exception {
}
// 设置安全拦截设置的地方
@Override
protected void configure(HttpSecurity http) throws Exception {
// http.authorizeRequests().anyRequest().authenticated() 返回一个拦截注册器
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/app/**").permitAll()///app/**所有人都可以访问,不受安全框架拦截
.antMatchers("/captcha.jpg").permitAll()//不要把图片给拦住了
.anyRequest().authenticated()
.and()
// 登陆页面为 /myLogin.html,并且登陆页不设权限
//csrf是跨域保护功能
.csrf().disable()
//登陆的页面为;
.formLogin().loginPage("/myLogin.html").permitAll()
.loginProcessingUrl("/auth/form").permitAll()//你记住这个函数参数和登陆form表单要一致就行
.failureHandler(new MyAuthenticationFailureHandler())
.and()
.sessionManagement().maximumSessions(1); //好像是设置session
//将验证码过滤器放在UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
7、前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="login"></div>
<h2>登陆</h2>
<!-- 这里的action要和安全框架里面的loginProcessingUrl()参数一样-->
<form action="/auth/form" method="post">
<input type="text" name="username" placeholder="username">
<input type="password" name="password" placeholder="password">
<div style="display: flex">
<!-- 新增图形验证码地输入框-->
<input type="text" name="captcha" placeholder="captcha">
<!-- 图片指向图形验证码API-->
<img src="/captcha.jpg" alt="captcha" height="50px" width="150px" style="margin-left:20px ">
</div>
<input type="submit" value="login">
</form>
</body>
</html>
到目前为止的代码
https://github.com/E-10000/spring-security-demo/tree/demo02
四、自动登陆和注销
spring security已经帮我们做好自动登陆和注销了
1、自动登陆
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService userDetailsService;
// 设置安全拦截设置的地方
@Override
protected void configure(HttpSecurity http) throws Exception {
//xxxxx
http
.rememberMe()
//userDetailsService是干嘛的?\
.userDetailsService(userDetailsService)
//rememberMeParameter参数为登陆页面的name属性
.rememberMeParameter("remember")
// xxxxx
}
rememberMe()
启动自动登陆功能rememberMeParameter("remember")
我们是已经定制页面了,所以要知道前端哪个按钮是记住登陆的功能
2、前端
xxx
<input type="checkbox" name="remember">记住我 <br>
xxxx
通过input
的name
属性和rememberMeParameter('')
里面的参数关联起来
3、登陆效果
登陆后,可见多了一个remember-me
4、持久化令牌
我也不是很懂,反正就是说这样有点不安全,用令牌就安全一点
先创建数据库
create table persistent_logins(
username varchar(64) not null,
series VARCHAR(64) PRIMARY key,
token VARCHAR(64) not NULL,
last_used TIMESTAMP not NULL
);
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private MyUserDetailsService userDetailsService;
// 设置安全拦截设置的地方
@Override
protected void configure(HttpSecurity http) throws Exception {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//xxxxx
http
.rememberMe()
//userDetailsService是干嘛的?\
.userDetailsService(userDetailsService)
//rememberMeParameter参数为登陆页面的name属性
.rememberMeParameter("remember")
//tokenRepository 定制token,在数据库中记录token
.tokenRepository(jdbcTokenRepository)
// xxxxx
}
tokenRepository(jdbcTokenRepository)
:定制token,在数据库中记录token
登陆之后
5、注销登陆
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private MyUserDetailsService userDetailsService;
// 设置安全拦截设置的地方
@Override
protected void configure(HttpSecurity http) throws Exception {
//xxxxx
http
.logout()
//注销成功后,重定向到该路径下
.logoutSuccessUrl("/")
//指定接受注销请求的路由,默认注销地址为/logout
.logoutUrl("/MyLogout")
//就可以更加高级地定制
//下面这些高级设置,用了之后logoutSuccessUrl("/")就不会重定向了,但是这些高级设置用在哪里我也不知道,所以就不要用他了。。。
// .logoutSuccessHandler(new LogoutSuccessHandler() {
// @Override
// public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//
// }
// })
// //使该用户地HttpSession失效
// .invalidateHttpSession(true)
// //注销成功,删除指定地cookie
// .deleteCookies("cookie1","cookie2")
// //用于注销的处理语句,允许自定义一些清理策略
// .addLogoutHandler(new LogoutHandler() {
// @Override
// public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
//
// }
// })
}
logoutSuccessUrl("/")
:注销成功后,重定向到该路径下logoutUrl("/MyLogout")
:指定接受注销请求的路由,默认注销地址为/logout
我们就在登陆之后就跳转到一个页面,那个页面有个登陆按钮,改下控制类
控制类
@Controller
public class MainController {
@GetMapping("/")
public String hello(){
System.out.println("hello");
//重定向会index.html
return "redirect:index.html";
}
@ResponseBody
@GetMapping("/test")
public String test(){
//直接输出test
return "test";
}
@ResponseBody
@GetMapping("/admin")
public String admin(){
return "hello admin";
}
@ResponseBody
@GetMapping("/user")
public String user(){
return "hello user";
}
@ResponseBody
@GetMapping("app")
public String app(){
return "hello app";
}
}
登陆之后的主页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/MyLogout" method="post">
<input type="submit" value="注销">
</form>
</body>
</html>
点了注销后返回http://localhost:8080/myLogin.html
,数据库的记录也会删除
登陆与注销代码https://github.com/E-10000/spring-security-demo/tree/demo03
五、会话管理
会话嘛。就是保存session和cookie的那个东西,是验证你有没有登陆的
1、防御会话固定攻击
@Override
protected void configure(HttpSecurity http) throws Exception {
http//开启会话管理
.sessionManagement()
/*
防御固定攻击策略有none,newSession,migrateSession,changeSessionId
*/
.sessionFixation().none()
}
sessionManagement
有4中策略
none
:不做任何变动,登陆之后还是用旧的sessionnewSession
:登陆之后创建一个新的sessionmigrateSession
:登陆之后创建一个新的session,并且将旧的session中的数据复制过来changeSessionId
:不创建新的会话,由Servlet容器提供的会话固定保护
2、会话过期
/开启会话管理
@Override
protected void configure(HttpSecurity http) throws Exception {
http//开启会话管理
.sessionManagement()
/*
防御固定攻击策略有none,newSession,migrateSession,changeSessionId
*/
.sessionFixation().none()
//会话过期默认方法①:跳转到某个URL
.invalidSessionUrl("/myLogin.html")
//②自定义会话过期策略,默认过期时间为30M,可以改,但最少60s
// .invalidSessionStrategy(new MyInvalidSessionStrategy())
}
.invalidSessionUrl("/myLogin.html")
方法1,过期后添砖到某个URL.invalidSessionStrategy(new MyInvalidSessionStrategy())
方法2,过期后跳转到自定义过期策略,那个类就自己创建的
//实现invalidSessionStrategy接口,自定义会话过期策略
public class MyInvalidSessionStrategy implements InvalidSessionStrategy {
@Override
public void onInvalidSessionDetected(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException {
//但是设置了这个之后,连登陆界面都变成了session无效,目前还不知道怎么设置这部分
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write("session无效");
// httpServletResponse.setHeader("refresh","3;/myLogin.html");
}
}
默认30分钟没有活动会话便会失效,可以通过YML修改过期时间
server:
servlet:
session:
timeout: 60 #会话过期时间60s
3、会话并发
你就加上这个OK了
.and()
// 最大会话数为1
.sessionManagement().maximumSessions(1);
到目前为止的代码https://github.com/E-10000/spring-security-demo/tree/demo04
4、持久层(登陆使用数据库)的会话并发 (不会!!)
一大堆理论俺也不知道,反正就是重写user类的hashCode和equals两个方法就可
//不要用@Data ,因为会重写了hashCode,我们还要自己写这个函数呢
@AllArgsConstructor//满参构造器
@NoArgsConstructor//无参构造器
@Setter//set
@Getter//get
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private String roles;
private boolean enable;
private List<GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
// @Override
// public String getPassword() {
// return this.password;
// }
//
// @Override
// public String getUsername() {
// return this.username;
// }
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enable;
}
//重写equals和hashCode,实现持久层数据库连接登陆的会话并发控制
@Override
public boolean equals(Object obj) {
return obj instanceof User ? this.username.equals(((User)obj).username):false;
}
@Override
public int hashCode() {
return this.username.hashCode();
}
}
六、spring security用数据库中的账号(持久层)进行登陆
1、User类
该类继承UserDetails
@AllArgsConstructor//满参构造器
@NoArgsConstructor//无参构造器
@Setter//set
@Getter//get
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private String roles;
private boolean enable;
private List<GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enable;
}
//重写equals和hashCode,实现持久层数据库连接登陆的会话并发控制
@Override
public boolean equals(Object obj) {
return obj instanceof User ? this.username.equals(((User)obj).username):false;
}
@Override
public int hashCode() {
return this.username.hashCode();
}
}
2、数据库中的类型与数据
类型
数据
user的密码,是Bcrypt密码(11)
加密的
3、新建UserDetailsService类
该类继承UserDetailsService
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 从数据库读取用户
User user = userMapper.findByUsername(s);
//用户不存在时抛出异常
if (user == null){
throw new UsernameNotFoundException("用户不存在");
}
//将数据库形式的roles解析为UserDetails的权限
user.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles()));
return user;
}
//实行权限的转换
private List<GrantedAuthority> generateAuthorities(String roles){
List<GrantedAuthority> authorities = new ArrayList<>();
String[] roleArray = roles.split(";");
if (roles !=null && !"".equals(roles)){
for (String role:roleArray) {
authorities.add(new SimpleGrantedAuthority(role));
}
}
return authorities;
}
}
-
这些代码,用断点来看,他们也不会经过这里,尽管他们使用到这个类,真实非常神奇呢
-
其中,mapper层是
@Mapper public interface UserMapper { @Select("select * from users where username=#{username}") User findByUsername(@Param("username") String username); }
只有一个方法,根据用户名找用户信息
4、安全框架里面设置数据持久层
这是最重要的一步!!!
我就是在这里,没有设置导致徘徊了好久
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//设置了userDetailsService后,要用
auth
.userDetailsService(userDetailsService)
.passwordEncoder(encoder());
}
@Bean
public PasswordEncoder encoder(){
return new BCryptPasswordEncoder(11);
}
}
//xxxxxx
configure(AuthenticationManagerBuilder auth)
,在这里类里面,有个重要的函数userDetailsService()
,设置了用到哪个自定义的数据持久层- 然后这个必须是加密的嘛,就是你登陆的时候,你输入了账号
user
密码123
,但是spring security必须要我们使用加密处理,所以这里用到加密 encoder()
这个方法,抄就好了,唯一变的就是那个数字而已。然后你用到的就是BCrypt(11)加密方式。然后在configure
方法中设置好。
七、在注册的时候进行密码加密
这个也很简单啦,因为spring security已经帮我们做好了
在安全框架的时候进行@Bean
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//注册的时候或者新建的时候,用到BC加密
@Bean
PasswordEncoder passwordEncoder(){
PasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder(11);
return bcryptPasswordEncoder;
}
}
在注册控制类里面进行设置,我这里只做了个demo
@Controller
public class MainController {
//!!!!一定渝澳注意这里!!!
@Autowired
private PasswordEncoder passwordEncoder;
@ResponseBody
@GetMapping("/test")
public String test(){
//对"123"进行加密,加密的方法设置在spring security类中
String encode = passwordEncoder.encode("123");
return encode;
}
}
到目前为止的代码
https://github.com/E-10000/spring-security-demo/tree/demo05
八、跨域之CORS
跨域概述
就普通地跨域是不可以的嘛,浏览器有保护机制,我们要做的就是要做到哪些跨域之间的允许的,哪些之间是不给跨域的(保护自己)
以下有3种跨域方式:
http://www.a.baidu.com
下访问https://www.a.baidu.com
协议跨域a.baidu.com
下访问b.baidu.com
主机跨域baidu.com:80
下访问baidu.com:8080
端口跨域
1、CORS
使用CORS方法进行跨域
import org.springframework.web.cors.*;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 设置安全拦截设置的地方
@Override
protected void configure(HttpSecurity http) throws Exception {
//启用CORS支持
http.cors()
//xxxxxxx
}
@Bean
//抄就完事了
//这里导包会出错,所以我就import org.springframework.web.cors.*;选择导入了全部
CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration configuration = new CorsConfiguration();
//允许从百度站点跨域
configuration.setAllowedOrigins(Arrays.asList("https://www.baidu.com"));
//允许使用GET和POST方法
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
//允许带凭证
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//对所有URL生效
source.registerCorsConfiguration("/**",configuration);
return source;
}
}
九、CSRF
1、CSRF的攻击过程
比如有个用于点赞的API,传如文章ID就可以点赞,那么有个人发一个说说,只有一个图片,但是图片的URL为点赞的API。那么,每个浏览过的人都会默认点赞了,即使你一直往下滑。然后我们就是要做好这个的保护。
然后书本说一大堆。。。我直接放代码吧,就一行
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
//这个开启CSRF保护,但是开了我登陆不了,算了算了
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
}
}