SpringSecurity<3>

在springsecurity中如何获取当前用户
我们可以看看 SecurityContext  这个Security内容

我们可以用

在这里插入图片描述

SecurityContextHolder----->根据不同策略 选择不同的
SecurityContextHolder利用了一个SecurityContextHolderStrategy(存储策略)进行上下文的存储

在这里插入图片描述

在这里插入图片描述

当我们认证成功后就可以通过 SecurityContextHolder 拿到当前的认证信息, 这里的底层是通过threadLocal 
默认的策略就是用的threadLocal  当前线程的子线程中就获取不到了 

在这里插入图片描述

可以设置一下这个
System.setProperty("spring.security.strategy","MODE_THREADLOCAL");

在这里插入图片描述

  上面的这个是单线程版本 就是说 threadLocal 只会在当前线程中获取到用户信息, 但是如果你此时开线程的话就不行了

在这里插入图片描述

 看如果此时开线程了就是不行了 因为threadLocal 只能获取到当前线程的信息 
----------------->>> 我们可以调整当前策略
System.setProperty("spring.security.strategy","MODE_INHERITABLETHREADLOCAL");
 多线程环境   适用于多线程环境 []如果希望在子线程中也可以获取到登录用户数据 呢么也可以调成这种模式了


在这里插入图片描述

我们在页面上是怎么获取到登录用户呢》~~~~~~~  我们可以用
springsecurity 整合thymealf的pom 

在这里插入图片描述

===============================================>>>>
自定义认证数据源

在这里插入图片描述

所以 我们需要拿到 AuthenticationManager 以及自定义数据源

我们可以通过
 protected void configure(AuthenticationManagerBuilder auth) 
拿到当前的AuthenticationManager  对象 并且@Bean设置一个内存类型的数据源进去

在这里插入图片描述

 这样的话我们使用这个自定义的AuthenticationManager 并且自定义 一个内存用户 交给SpringSecurity了 

------------------------------------------>>>>>>>>>>>>>>>>>>
 这就是自定义内存的数据源 我们需要自定义数据库的数据源

在这里插入图片描述

我们如果想要自定义数据库的实现 只需要吧数据源这块切换成我们自己的
数据库表


-- 用户表
CREATE TABLE `user`
(
    `id`                    int(11) NOT NULL AUTO_INCREMENT,
    `username`              varchar(32)  DEFAULT NULL,
    `password`              varchar(255) DEFAULT NULL,
    `enabled`               tinyint(1) DEFAULT NULL,
    `accountNonExpired`     tinyint(1) DEFAULT NULL,
    `accountNonLocked`      tinyint(1) DEFAULT NULL,
    `credentialsNonExpired` tinyint(1) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- 角色表
CREATE TABLE `role`
(
    `id`      int(11) NOT NULL AUTO_INCREMENT,
    `name`    varchar(32) DEFAULT NULL,
    `name_zh` varchar(32) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- 用户角色关系表
CREATE TABLE `user_role`
(
    `id`  int(11) NOT NULL AUTO_INCREMENT,
    `uid` int(11) DEFAULT NULL,
    `rid` int(11) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY   `uid` (`uid`),
    KEY   `rid` (`rid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- 插入用户数据
BEGIN;
  INSERT INTO `user`
  VALUES (1, 'root', '{noop}123', 1, 1, 1, 1);
  INSERT INTO `user`
  VALUES (2, 'admin', '{noop}123', 1, 1, 1, 1);
  INSERT INTO `user`
  VALUES (3, 'blr', '{noop}123', 1, 1, 1, 1);
COMMIT;
-- 插入角色数据
BEGIN;
  INSERT INTO `role`
  VALUES (1, 'ROLE_product', '商品管理员');
  INSERT INTO `role`
  VALUES (2, 'ROLE_admin', '系统管理员');
  INSERT INTO `role`
  VALUES (3, 'ROLE_user', '用户管理员');
COMMIT;
-- 插入用户角色数据
BEGIN;
  INSERT INTO `user_role`
  VALUES (1, 1, 1);
  INSERT INTO `user_role`
  VALUES (2, 1, 2);
  INSERT INTO `user_role`
  VALUES (3, 2, 2);
  INSERT INTO `user_role`
  VALUES (4, 3, 3);
COMMIT;
引入jdbc 以及数据库的依赖
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.2.0</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.38</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.2.7</version>
</dependency>
//  User对象
public class User  implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Boolean enabled;
    private Boolean accountNonExpired;
    private Boolean accountNonLocked;
    private Boolean credentialsNonExpired;
    private List<Role> roles = new ArrayList<>();

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        roles.forEach(role->grantedAuthorities.add(new SimpleGrantedAuthority(role.getName())));
        return grantedAuthorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }
		//get/set....
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.baizhi.dao.UserDao">
    <!--查询单个-->
    <select id="loadUserByUsername" resultType="User">
        select id,
               username,
               password,
               enabled,
               accountNonExpired,
               accountNonLocked,
               credentialsNonExpired
        from user
        where username = #{username}
    </select>

    <!--查询指定行数据-->
    <select id="getRolesByUid" resultType="Role">
        select r.id,
               r.name,
               r.name_zh nameZh
        from role r,
             user_role ur
        where r.id = ur.rid
          and ur.uid = #{uid}
    </select>
</mapper>

mapper文件

在这里插入图片描述

 然后我们自己实现一个 UserDetailsService  并且吧这个类注入到Spring容器中

在这里插入图片描述

这样我们就实现了切换数据源  切换到jdbc了

 当我们输入admin/123就进入我们的系统了
 ----------------------------------------------------------->>>>>>>>>>>>>>>>>>>>>
接下来我们使用传统web开发就是前后端不分离的  页面+系统
我们做一个用户登录  进去显示首页 然后退出的功能
/*
 * 自定义springsecurity的 相关配置  springsecurity 的相关配置
 *
 * */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  @Autowired
  MyUserDetailService myUserDetailService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 设置数据源
        auth.userDetailsService(myUserDetailService);//
    }

    /* http 用来去控制http 请求的*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.

                authorizeRequests() // 开启认证
                //  放行login.html请求~
                .mvcMatchers("/login.html").permitAll() // 当时login.html  放行
                .anyRequest()   // 所有请求开启认证
                .authenticated() // 所有请求都的认证
                .and()
                .formLogin() // 表单认证
                .loginPage("/login.html")// 指定自定义登录页面
                /*拦截doLogin 请求*/
                .loginProcessingUrl("/doLogin") // 请求url
                .usernameParameter("uname")
                .passwordParameter("passwd")
                .defaultSuccessUrl("/index.html", true)/*认证成功后跳转到哪里*/
                .failureForwardUrl("/login.html") /*重定向到我我们的登录页面  吧信息存储在session 作用域中*/
                .and()
                .logout()/*开启退出登录*/
                /**/
                .logoutUrl("/logout")/*路径 默认get 请求*/
                .logoutSuccessUrl("/login.html")/*退出之后回到login.html*/
                /* 退出成功的页面*/
                .and()
                .csrf()
                .disable();/* csrf漏洞保护给关闭了*/

    }

}

```bash
我们用视图解析器将 页面与请求挂钩 
这样访问 login.html ---->>>  请求就会路由到login 地址

在这里插入图片描述
login页面在这里插入图片描述

主页加上退出登录的按钮~

在这里插入图片描述

 一样自定义数据源 以及sql 从上面拿就可以了~ 


呢么接下来我们看一下如果前后端分离的 配置吧~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在我们 前后端分离的时候 我们知道  前后端交互的都是json格式的数据
也就是说前端发送的是Json格式的请求报文,后台也是json格式的响应报文
后台此时接受前端数据 就得从body中获取,此时就不像 前后端不分离呢样
通过 request.getParameter(this.passwordParameter); 获取对应的认证信息  因为 body中的请求数据我们需要用 流或者@Requestbody 来从Request 中获取~
 我们只需要吧这段代码 替换了就可以了~~~~~~~~~ 
在前后端不分离的 时候前后端交互  

在这里插入图片描述

---------------------------------------------------------------
我们要 写一个filter来替换UsernamePasswordAuthenticationFilter
吧这个filter中的从请求体重拿请求参数的 就给他替换为~~~~流来处理
判断当前请求数据如果是json格式的话 或者从请求体中的数据的话 我们就得用流来解析  第二我们还得替换
//@Component
/*自定义前后端分离的Filter*/
public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    /*复写  回调父类*/
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response)
            throws AuthenticationException {
        System.out.println("<<<<<<<<<<,----------------------->>>>>>>>>");
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        ///  如果是请求体的body的输 我就用流来接受 并且 我自己处理  否则调用父类的去处理  --->>> 下一个就是UsernamePasswordAuthenticationFilter
        
        if (MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType())) {
            String username = null; // 不能写死
            String password = null;
            try {
                /*用户名和密码*/
                Map<String, String> userInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class);
                //  以后我也可以通过注入的方式
                username = userInfo.get(getUsernameParameter());
                password = userInfo.get(getPasswordParameter());
            } catch (IOException e) {
                e.printStackTrace();
            }
            / 我们已经拿到用户名了 拿到用户名就仿照     分装token
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
            /* 我已经拿到 manager了*/
            return this.getAuthenticationManager().authenticate(authRequest);
        }
        // 调用  UsernamePasswordAuthenticationFilter
        return super.attemptAuthentication(request, response);
    }
}
SpringSecurity 的配置类


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /*自定义的loginFilter交给工厂管理  这个Filter 接管功能  1. 替换 2,位置不变
     *
     *
     *
     * */
//    @Autowired
//    LoginFilter loginFilter;  自定义fitetr
    @Autowired
    MyUserDetailService myUserDetailService; // 自定义数据库的实现

    /* http 用来去控制http 请求的*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
                authorizeRequests()// 开启认证
                .anyRequest().authenticated()// 任何请求都必须认证  所有请求必须认证
                // 当我们  所有请求 该怎么认证 原来我们的认证都是formLogin()
                .and()
                /*还是formLogin 只不过此时底层实现我们的变化*/
                .formLogin()/*原来我们也是走formLogin  只是现在formLogin 不能走UserNamePassword */
                .and()
                .exceptionHandling()// 异常的处理

                /// 当你没有进行认证的时候 我们会出现一个认证异常  出现认证异常的时候 我们只想
                // 给一个认证异常 [没有认证的资源]  抛出异常之后就不会再有生成这个登录页面了~
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest request,
                                         HttpServletResponse response,
                                         AuthenticationException authException) throws IOException, ServletException {

//                        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                        /*未被认证的*/
                        response.setStatus(HttpStatus.UNAUTHORIZED.value());
                        response.getWriter().println("请认证 ~之后再做处理");
                    }
                })
                .and()
                .logout()/*退出*/
//                .logoutUrl("/logout") /*退出url 这个是默认的 get 方式 的/logout 我需要注释了~  换成 delete以及post方式 的logout */
                .logoutRequestMatcher(new OrRequestMatcher(
                        /*退出  delete name*/
                        new AntPathRequestMatcher("/logout", HttpMethod.DELETE.name()),
                        new AntPathRequestMatcher("/logout", HttpMethod.GET.name())
                ))
                /*退出的handler*/

                .logoutSuccessHandler((req, rsp, authentication) -> {
                    HashMap<Object, Object> resMap = new HashMap<>();
                    resMap.put("msg", "注销成功");
                    //  强转成用户对象
                    resMap.put("userInfo", authentication.getAuthorities());
                    /   响应码
                    rsp.setStatus(HttpStatus.OK.value());
                    rsp.setContentType("application/json; charset=UTF-8");
                    String stringRsult = new ObjectMapper().writeValueAsString(resMap);
                    rsp.getWriter().println(stringRsult);
                })
                .and()
                .csrf().disable();// 关闭
        /*认证成功  设置formLoginin UsernamePasswordAuthenticationFilter 的*/
        //   .successHandler()
        /*认证失败*/
        // .failureHandler()
        //  我们现在就像让我们的loginFilter 替换usernamePasswordFilter  我还是

        // 我还是所有请求认证 我还是form表单认证他还是会找 UsernamePasswordAuthenticationFilter 已经被我替换成login的实现
        // 所以到最后就转到loginFilter
        /------->>> 你工厂中一创建的时候我就通过set方法给你给了  以后你发请求的时候    用get 和sert
        //loginFilter.setUsernameParameter("uname");//指定接受json用户名的key
        // loginFilter.setPasswordParameter("passwd");//指定接受json 密码的key

        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
        //用某个Filter去替换过滤器链中的那个Filter
        // before  放在过滤器链中那个之前
        // after  放在过滤器链中那个之后
    }
// 配置自己的filter 现在就得配置自己filter 的成功以及失败响应了 而不用默认的'
//.successHandler()

//.failureHandler()
    @Bean
    public LoginFilter loginFilter() throws Exception {
        /*认证成功  设置formLoginin UsernamePasswordAuthenticationFilter 的*/
        //   .successHandler()
        /*认证失败*/
        // .failureHandler()


        LoginFilter loginFilter = new LoginFilter();
        // --------->>>>>>>>>>>>>认证的Url
        //  当我们前端接受doLogin 的请求时候 他就会这个 请求中传递的uname  passwd
        ///  然后通过Filter 认证
        loginFilter.setFilterProcessesUrl("/doLogin");

        loginFilter.setUsernameParameter("uname");
        loginFilter.setPasswordParameter("passwd");
        // 注入authenticationManager  因为要求 自己的authenticationManager 去执行  authenticate  方法
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        /// 成功 认证成功的处理  --------->>>> 自定义成功的处理
        loginFilter.setAuthenticationSuccessHandler((req, rep, authentication) -> {
            HashMap<Object, Object> resMap = new HashMap<>();
            resMap.put("msg", "登录成功");
            //  强转成用户对象
            resMap.put("userInfo", authentication.getAuthorities());
            rep.setStatus(HttpStatus.OK.value());
            rep.setContentType("application/json; charset=UTF-8");
            String stringRsult = new ObjectMapper().writeValueAsString(resMap);
            rep.getWriter().println(stringRsult);
        });
        /// 失败的 认证成功的处理  一般我们会改变响应的状态码
        /  自定义失败的处理
        loginFilter.setAuthenticationFailureHandler((req, rep, exception) -> {
            HashMap<Object, Object> resultMap = new HashMap<>();
            /*失败信息*/
            resultMap.put("msg", "登录失败" + exception.getMessage());
            rep.setContentType("application/json; charset=UTF-8");
            // 改变响应码
            rep.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            String stringRsult = new ObjectMapper().writeValueAsString(resultMap);
            rep.getWriter().println(stringRsult);
        });
        return loginFilter;
    }

    /*自定义mananger  但是不能被外部使用  如果我们要暴露出来的化 我们必须要覆盖这个方法*/
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailService);
    }

    /*我们要暴露出来这个AuthenticationManager 的时候*/
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /* UserDetailsService 使用内存数据库*/
//    @Bean
//    public UserDetailsService userDetailsService() {
//        UserDetails serDetails = User.withUsername("user")
//                .password("{noop}123").roles("admin").build();
//        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
//        inMemoryUserDetailsManager.createUser(serDetails);
//        return inMemoryUserDetailsManager;
//
//    }
}

/*以后 所有的请求 前后端分离的请求都要认证 认证就是form 表单认证
  form表单认证底层用的UsernamePasswordAuthenticationFilter
 *  已经被我替换成login 了 {准确的说在 UsernamePasswordAuthenticationFilter 之前的  LoginFilter]
// 如果是p 
 回头再访问的时候  就会用 LoginFilter 来进行访问
 * 使用uname pward 来进行验证
 * 认证成功之后我们就会进行json 的setAuthenticationSuccessHandler回调函数
 * 认证失败之后我们就会进行json 的setAuthenticationFailureHandler回调函数
 * */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值