spring secutiry个人笔记

	<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

通过inputname属性和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 :不做任何变动,登陆之后还是用旧的session
  • newSession:登陆之后创建一个新的session
  • migrateSession:登陆之后创建一个新的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())
         }
}

猜你喜欢

转载自blog.csdn.net/yi742891270/article/details/108773739