ps:此篇是我根据编程不良人的springsecurity学习视频学习的笔记
地址如下:https://www.bilibili.com/video/BV1z44y1j7WZ?p=28&spm_id_from=pageDriver&vd_source=c5dc98a1da2912992b967e875b197e56
加了这个依赖之后,不用做任何操作,就会对系统进行保护
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
输入写好的hello地址,会自动跳转到login地址
登录hello地址
直接跳转到登录login页面
原理(省略,以后再细看)
是因为springsecurity内置的一系列filter过滤器实现的
因为springbootWebSecurityConfiguration里面对所有请求都做了权限控制
这个是默认配置
自带登陆界面
UserDetailService
我们的需求是公共资源不限制,可以直接访问,而显然springsecurity不符合我们的需求。
自定义认证(WebSecurityConfigurerAdapter)
可以继承WebSecurityConfigureAdapter去重写configure方法,去自定义资源权限的规则,去完成一个不同的请求权限处理
上面图片的代码实现
@Configuration
public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//表示放行
.mvcMatchers("/index").permitAll()//放行资源要写在任何前面
//其他都要认证
.anyRequest().authenticated()
.and()
//开启表单认证,就是上面一开始说的login页面
.formLogin();
}
}
自定义登陆页面(loginPage)
原本是字符串拼接的响应界面
使用了 loginPage(“/login.html”)来自定义登录界面,并且在template中新建了一个login.html完成后如下图,登陆界面改变了。
新建一个login.html
@Configuration
public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//表示放行
.mvcMatchers("/login.html").permitAll()
.mvcMatchers("/index").permitAll()//放行资源要写在任何前面
//其他都要认证
.anyRequest().authenticated()
.and()
//表单
.formLogin()
//自定义登陆界面
.loginPage("/login.html")
.loginProcessingUrl("/dologin")//指定登录的url
// .successForwardUrl("/hello") //认证成功后 forward跳转到hello路径 始终在认证成功后跳转到指定请求
// .defaultSuccessUrl("/index") //认证成功后 redirect跳转到index 根据上一次保存的请求重新跳转
.successHandler(new MyAuthenticationSuccessHandler())//认证成功处理前后端分离
.and()
.csrf().disable();//禁止csrf跨站请求保护
}
}
自定义认证成功跳转逻辑(MyAuthenticationSuccessHandler)
前后端不分离,使用successForwardUrl和defalutSuccessUrl,这样登陆成功就可以指定到你需要的页面
前后端分离,使用successHandler,需要传一个AuthenticationSuccessHandler,这时候我们就不是跳转某个路径,而是返回一个接口。这就需要我们去新建一个MyAuthenticationSuccessHandler类去实现AuthenticationSuccessHandler接口,并重写onAuthenticationSuccess方法
/*
自定义认证成功之后的处理
*/
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
HashMap<String, Object> result = new HashMap<>();
result.put("msg","登陆成功");
result.put("status",200);
result.put("authentication",authentication);
httpServletResponse.setContentType("application/json;charset=UTF-8");
String s = new ObjectMapper().writeValueAsString(result);
httpServletResponse.getWriter().println(s);
}
}
定义认证失败跳转逻辑
forward:跳转是存在request作用域中的,登录页面的url是不变化的
redirect:重定向是存在session中的,登录页面的url是变化的,如下图所示
// .failureForwardUrl("/login.html") //认证失败后 forward跳转到login路径 始终在认证失败后跳转到指定请求 存在request
.failureUrl("/login.html")//认证失败后 redirect跳转到login 根据上一次保存的请求重新跳转 存在session
使用forward一开始的url是:localhost:8000/login.html
之后是/dologin
使用redirect一开始的url是:localhost:8000/login.html,如上图一致
之后是/login.html,是变化成我们设置的
自定义前后端分离认证失败跳转逻辑(failureHandler)
前后端分离,使用failureHandler,需要传一个AuthenticationFailureHandler,这时候我们就不是跳转某个路径,而是返回一个接口。这就需要我们去新建一个MyAuthenticationFailureHandler类去实现AuthenticationFailureHandler接口,并重写onAuthenticationfailure方法,返回的是json字符串
/**
* 自定义认证失败
*/
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("msg","登陆失败"+e.getMessage());
hashMap.put("status",500);
response.setContentType("application/json;charset=utf-8");
String s=new ObjectMapper().writeValueAsString(hashMap);
response.getWriter().println(s);
}
}
返回的是json字符串
定义认证成功后注销(logoutUrl)
这些不设置的话,也是默认设置成这样的
前后端分离自定义认证成功后注销
写一个类去实现LogoutSuccessHandler,重写 onLogoutSuccess方法,返回给前端一个json对象,成功后如下图
/**
* 自定义注销成功处理
*/
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
HashMap<String, Object> result = new HashMap<>();
result.put("msg","注销成功,当前认证对象为:"+authentication);
result.put("status",200);
httpServletResponse.setContentType("application/json;charset=UTF-8");
String s = new ObjectMapper().writeValueAsString(result);
httpServletResponse.getWriter().println(s);
}
}
注销默认的url是/logout
登录用户数据获取(SecurityContextHolder)
@RequestMapping(value = "/hello")
public String hello(){
System.out.println("hello");
//获取认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
System.out.println("身份信息:"+user.getUsername());
System.out.println("权限信息:"+authentication.getAuthorities());
return "hello";
}
输出结果,权限没设置所以没有
自定义认证数据源
认证的核心类AuthenticationManager
增加自定义UserDetailsService实现类 将默认从内存中获取的数据改为数据库中获取
使用springboot对security默认配置中,在工厂中默认创建AuthenticationManager,我们只需要去配置一个Bean,userDetailsService(),去配置上身份信息,AuthenticationManagerBuilder会去检测你当前工厂中有没有userDetailsService,有就会自动给springboot对security默认配置中。我们不需要再去默认配置中写userDetailsService。
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
userDetailsService.createUser(User.withUsername("aaa").password("{noop}123").roles("admin").build());
return userDetailsService;
}
//springboot对security默认配置中,在工厂中默认创建AuthenticationManager
@Override
protected void initialize(AuthenticationManagerBuilder builder) {
System.out.println("springboot默认配置"+builder);
}
第二种是自定义AuthenticationManager,我们在工厂中虽然配置了userDetailsService的Bean,但是会检测不到,因为自定义里面,已经把工厂里面的覆盖掉了,我们需要去手动告诉他auth.userDetailsService(userDetailsService()),去使用我们工厂中的userDetailsService
//自定义AuthenticationManager
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("自定义AuthenticationManager"+auth);
auth.userDetailsService(userDetailsService());
}
默认全局AuthenticationManager
自定义全局AuthenticationManager
//自定义AuthenticationManager,并没有在工厂中暴露出来
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("自定义AuthenticationManager"+auth);
auth.userDetailsService(userDetailsService());
}
//用来在自定义的AuthenticationManager在工厂中暴露,可以在任何位置注入
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
自定义数据源实现认证
- 建立数据库表
-- 用户表
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;
-- 插入用户数据
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,'song','{noop}123',1,1,1,1);
-- 插入角色数据
INSERT INTO `role`
VALUES(1,'ROLE_product','商品管理员');
INSERT INTO `role`
VALUES(2,'ROLE_admin','系统管理员');
INSERT INTO `role`
VALUES(3,'ROLE_user','用户管理员');
-- 插入用户角色数据
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);
User表
Role表
user_role表
新建User类,需要实现UserDetails接口,新建role类
/**
* 自定义User
*/
//需要实现UserDetailService,需要返回一个UserDetail,为了能对应上所以实现UserDetail
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> role=new ArrayList<>();//关系属性,用来存储当前用户所有角色信息
//返回权限信息
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<SimpleGrantedAuthority> auth = new HashSet<>();
role.forEach(role->{
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(role.getName());
auth.add(simpleGrantedAuthority);
});
return null;
}
@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;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public List<Role> getRole() {
return role;
}
public void setRole(List<Role> role) {
this.role = role;
}
}
//角色类
@Data
public class Role {
private Integer id;
private String name;
private String nameZh;
}
新建一个MyUserDetailService类去实现UserDetailsService,去重写loadUserByUsername,返回一个User对象
@Component
public class MyUserDetailService implements UserDetailsService {
//doa--springboot---mybatis
@Autowired
UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.loadUserByUserName(username);
//查询用户
if(ObjectUtils.isEmpty(user))throw new UsernameNotFoundException("用户名不正确");
//查询权限信息
List<Role> roleByUid = userDao.getRoleByUid(user.getId());
user.setRole(roleByUid);
return user;
}
}
新建一个Doa层还有相应的mapper.xml
<?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.example.springsecuritytest.com.hb.Dao.UserDao">
<select id="loadUserByUserName" resultType="com.example.springsecuritytest.com.hb.entity.User">
select id,
username,
password,
enabled,
accountNonExpired,
accountNonLocked,
credentialsNonExpired
from user
where username=#{username}
</select>
<select id="getRoleByUid" resultType="com.example.springsecuritytest.com.hb.entity.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>
最后,要在配置类中,将自定义的AuthenticationManager的userDetailService换成自己配置的
结果:都是自己数据库存储的信息
ps:根据编程不良人的springsecurity学习视频学习
地址如下:https://www.bilibili.com/video/BV1z44y1j7WZ?p=28&spm_id_from=pageDriver&vd_source=c5dc98a1da2912992b967e875b197e56