在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回调函数
* */