- SpringSecurity的基本使用
- 初始化项目
- 新建springboot项目,选择springsecurity、web、mysql、mybatis依赖
- 配置数据库(数据库不存在需手动创建)
- 启动项目
- 编写一个接口,并访问
- 学习配置security
- 1.自定义登录
- 2.登录成功、登录失败处理
- 3.密码加密
- 4.自定义查询用户
- 1.内存中
- 2.数据库
- 5.角色和权限
- 权限
- **角色**
- IP控制
- 自定义403权限不足提示
- 6.注解
- @Secured
- @PreAuthorize()和@PostAuthorize()
- 7.记住我
- 8.退出登录
- jwt+oauth2待更新
配置数据库(数据库不存在需手动创建)org.springframework.boot spring-boot-starter-securityorg.springframework.boot spring-boot-starter-webmysql mysql-connector-javaorg.mybatis.spring.boot mybatis-spring-boot-starter2.2.0
spring.datasource.password=111111 spring.datasource.username=root spring.datasource.url=jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver启动项目
看到他,说明就没问题
默认用户名:user
默认密码:
当然现在登录成功是404,因为我们什么都没有写。
为什么我们会访问8080就是这个页面?
因为security默认是会要求所有请求认证。在我们没有被认证(即登录)时,任何请求默认都会重定向到这个登录页。
编写一个接口,并访问@RestController
public class TestController {
@GetMapping("/hello")
public String hello() {
return "hello world!";
}
}
访问:localhost:8080/hello
依然要先登录,然后才能看到
学习配置security 1.自定义登录前后端分离的话,可以看看这篇文章:https://blog.csdn.net/qq_42682745/article/details/121326021
我们的配置类需要继承WebSecurityConfigurerAdapter
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http);
http.formLogin() //开启表单登录
.loginPage("/login.html") //定义登录页面
.loginProcessingUrl("/mylogin") //自定义登录请求,form表单中的action就是它
.usernameParameter("myName") //自定义用户名参数,默认表单中必须是username
.passwordParameter("myPass"); //自定义密码参数,默认表单中必须是password
http.authorizeRequests() //认证相关
.antMatchers("/login.html").permitAll() //放行登录页面
.antMatchers("/Main.html").denyAll() //拒绝访问
.anyRequest().authenticated(); //其余请求都必须认证后才能访问
//从 Spring Security4 开始 CSRF 防护默认开启。默认会拦截请求。进行 CSRF 处理。
// CSRF 为了保证不是其他第三方网站访问,
// 要求访问时携带参数名为_csrf 值为 token(token 在服务端产生)的内容,如果token 和 服务端的 token 匹配成功,则正常访问。
//所以关闭csrf请求才能成功
http.csrf().disable();
}
}
- .antMatchers("/login.html").permitAll() 将matchers中的资源放行,不认证即可访问
- .anyRequest().authenticated() 除去上面matchers中的资源,其余资源访问都需要认证
随便写一个登录页面:
Title
ok,
现在我们访问:localhost:8080/hello , 被重定向到我们自定义的登陆页面了
,再输入user和那串密码,
成功
2.登录成功、登录失败处理successForwardUrl("/success") //登录成功的post处理接口
.failureForwardUrl("/unsuccess") //登录失败的post处理接口
失败:
成功:
3.密码加密PasswordEncoder核心接口,进入PasswordEncoder接口,鼠标双击类名,Ctrl+h,
可以看到,加密方式很多啊。
security官方推荐的是BCryptPasswordEncoder。
下面我们在测试类中用一下:使用BCryptPasswordEncoder加密123456
下面我们在测试类中用一下:使用NoOpPasswordEncoder加密123456
NoOpPasswordEncoder,就是不加密密码,只推荐测试使用;这个已经被标记为过时、不推荐使用。
4.自定义查询用户UserDetailsService、UserDetails接口就是核心,我们需要了解。
进入UserDetailsService接口,鼠标双击类名,Ctrl+h,
我们就了解,内存中查询用户和数据库查询用户
再看看UserDetails接口,
它就是security的一个用户单位,我们使用它的实现类User即可。注意,是security的User不是你自己写的。
1.内存中在刚刚的配置文件中,加上这两个方法(一个创建获取用户的bean,一个创建密码加密器bean):
@Bean
public UserDetailsService users() {
//创建内存UserDetailsManager对象
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//创建用户张三李四
manager.createUser(User.withUsername("张三").password("123").authorities("p1","p2").build());
manager.createUser(User.withUsername("李四").password("123").authorities("p3").build());
return manager;
}
@Bean
public PasswordEncoder encoder() {
return NoOpPasswordEncoder.getInstance();
}
张三,密码123,权限p1,p2
李四,密码123,权限p3
ok,现在我们试试用这两个账号登录
登录成功,下面我们再加上权限
http.authorizeRequests() //认证相关 .antMatchers("/login.html").permitAll() //放行登录页面 .antMatchers("/unsuccess").permitAll() //访问hello请求需要有p1权限 .antMatchers("/hello").hasAnyAuthority("p1") .anyRequest().authenticated(); //其余请求都必须认证后才能访问
先使用,张三(p1、p2权限),登录成功后,浏览器访问localhost:8080/hello
访问成功,因为张三有p1权限。
下面试试李四(p3),登录成功后,浏览器访问localhost:8080/hello
403,无权访问
2.数据库就是写一个service实现UserDetailsService,loadUserByUsername方法即可。调用mapper去数据库查
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询数据库
MyUser myUser = userMapper.findByName(username);
if (myUser == null) {
return null;
}
return new User(myUser.getName(),myUser.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(myUser.getAuth()));
}
}
AuthorityUtils.commaSeparatedStringToAuthorityList(“p1,p2”),这个方法会将字符串以逗号分隔为list
这里图方便,我就伪造数据了。。。。用户名是xp,就返回一个User对象,密码123456,权限p1,p2
@Servicepublic class MyUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if ("xp".equals(username)) { return new User(username,"123456", AuthorityUtils.commaSeparatedStringToAuthorityList("p1,p2")); } return null; }}
重启,登录xp
登录成功,并且访问/hello也是可以的
5.角色和权限 权限权限在前文已经涉及到了
.antMatchers("/hello").hasAnyAuthority("p1","p2") //有其中一个权限即可
相信大家很容易理解,有什么权限才能干什么事
角色其实和权限差不多
角色,必须以ROLE_ 开头,后面跟角色名
@Servicepublic class MyUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if ("xp".equals(username)) { return new User(username,"123456", AuthorityUtils.commaSeparatedStringToAuthorityList("p1,p2,ROLE_admin,ROLE_test")); } return null; }}
我们再改一下security的配置,加上:.antMatchers("/test").hasAnyRole(“admin”,“test”)
在配置类中就只需要写角色名,不能加ROLE_
http.authorizeRequests() //认证相关
.antMatchers("/login.html").permitAll() //放行登录页面
.antMatchers("/unsuccess").permitAll()
.antMatchers("/Main.html").denyAll() //拒绝访问
//访问hello请求需要有p1权限
.antMatchers("/hello").hasAnyAuthority("p1","p2")
.antMatchers("/test").hasAnyRole("admin","test")
.anyRequest().authenticated(); //其余请求都必须认证后才能访问
访问/test接口需要有角色admin或者test,有其中一个角色即可访问。登录xp,访问test
IP控制//指定ip才能访问
.antMatchers("/test").hasIpAddress("192.168.1.4")
localhost的ip是 0:0:0:0:0:0:0:1
127.0.0.1 的ip就是本身
自定义403权限不足提示https://www.bilibili.com/video/BV1gb4y1b7XE?p=20
自己写一个类,实现AccessDeniedHandler接口
将MyAccessDeniedHandler注入到配置类,然后
6.注解 @Secured作用于类、方法
验证是否有具有指定角色,有才能访问
角色必须是以ROLE_ 开头的
现在,将配置类的角色相关的注释掉
1.启动类,增加@EnableGlobalMethodSecurity(securedEnabled = true)
2.在test接口上加上
@Secured("ROLE_admin,ROLE_test")@GetMapping("/test")public String test() { return "您可以访问/test接口";}
重启,登录访问test,成功。
现在将test接口的角色,改一下
@Secured("ROLE_xx")@GetMapping("/test")public String test() { return "您可以访问/test接口";}
无权访问
值得一提的是,@Secured(“ROLE_admin,ROLE_test”)和配置中的hasAnyRole(“admin”,“test”)。虽然都能写多个角色。hasAnyRole(“admin”,“test”)只需要其中一个权限就能访问。但是@Secured(“ROLE_admin,ROLE_test”)必须全部具备才能访问
@PreAuthorize()和@PostAuthorize()它两也作用于方法或者类。
判断有不有权限
@PreAuthorize(),执行方法前判断
@PostAuthorize(),执行方法后判断
很明显,我们既然做权限控制,自然是使用@PreAuthorize()更多,它两的用法一样。
1.启动类注解:
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
2.修改test接口
//@Secured("ROLE_admin,ROLE_test")
@PreAuthorize("hasAnyAuthority('p1','p2')")
@GetMapping("/test")
public String test() {
return "您可以访问/test接口";
}
3.将配置类中权限控制相关的注释调
启动–登录–访问test–成功
修改test接口:
@PreAuthorize("hasAnyAuthority('p8')")
@GetMapping("/test")
public String test() {
return "您可以访问/test接口";
}
@PreAuthorize里面的写法和配置文件一样,接口都是有提示的
7.记住我我们需要在配置类中加上一些东西:
1.注入依赖:
@Autowired private MyUserDetailsService myUserDetailsService; @Autowired private DataSource dataSource; //配置好mysql参数后,springboot会自动装配一个DataSource @Autowired private PersistentTokenRepository persistentTokenRepository;
2.写bean方法:
@Bean
//返回持久化token到数据库的bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
//配置数据源
jdbcTokenRepository.setDataSource(dataSource);
//自动创建所需要的表,第二次启动需要注释掉不然报错
jdbcTokenRepository.setCreateTableonStartup(true);
return jdbcTokenRepository;
}
3.加上配置
http.rememberMe() //开启记住我功能
.rememberMeParameter("remember") //自定义记住我参数名,remember-me
.userDetailsService(myUserDetailsService) //指定查询用户的service对象
.tokenRepository(persistentTokenRepository); //指定token持久化对象
4.修改test接口,权限改回来
@PreAuthorize("hasAnyAuthority('p1')")
@GetMapping("/test")
public String test() {
return "您可以访问/test接口";
}
5.登陆页面修改
Title
重启,访问:
第二次启动记得将建表的代码注释掉
现在,我们关闭浏览器。直接访问:localhost:8080/test,看会不会让我们再登录。
直接访问到了,不会跳转到登录页面
保存到数据库的token有效期默认为两周,
我们也可以自己设置
.tokenValiditySeconds(10086) //设置token有效时间,单位秒
完整的配置类代码:
import javax.sql.DataSource;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private DataSource dataSource;
@Bean
public PasswordEncoder encoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
//返回持久化token到数据库的bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//自动创建所需要的表,第二次启动需要注释掉不然报错
//jdbcTokenRepository.setCreateTableonStartup(true);
return jdbcTokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http);
http.formLogin() //开启表单登录
.loginPage("/login.html") //定义登录页面
.loginProcessingUrl("/mylogin") //自定义登录请求,form表单中的action就是它
.usernameParameter("myName") //自定义用户名参数,默认表单中必须是username
.passwordParameter("myPass") //自定义密码参数,默认表单中必须是password
.successForwardUrl("/success")
.failureForwardUrl("/unsuccess");
http.authorizeRequests() //认证相关
.antMatchers("/login.html").permitAll() //放行登录页面
.antMatchers("/unsuccess").permitAll()
.antMatchers("/Main.html").denyAll() //拒绝访问
//访问hello请求需要有p1权限
.antMatchers("/hello").hasAnyAuthority("p1","p2")
//.antMatchers("/test").hasAnyRole("admin","test")
.anyRequest().authenticated(); //其余请求都必须认证后才能访问
http.rememberMe() //开启记住我功能
.tokenValiditySeconds(10086) //设置token有效时间,单位秒
.rememberMeParameter("remember") //自定义记住我参数名,remember-me
.userDetailsService(myUserDetailsService) //指定查询用户的service
.tokenRepository(persistentTokenRepository()); //token持久化
//从 Spring Security4 开始 CSRF 防护默认开启。默认会拦截请求。进行 CSRF 处理。
// CSRF 为了保证不是其他第三方网站访问,
// 要求访问时携带参数名为_csrf 值为 token(token 在服务端产生)的内容,如果token 和服务端的 token 匹配成功,则正常访问。
//所以关闭csrf请求才能成功
http.csrf().disable();
}
}
8.退出登录
退出就很简单了,我们找个页面,写个a标签都可以,security默认的退出就是"/logout"
Title
登录成功退出
如果你想自定义退出路径也可以:
即可。
jwt+oauth2待更新


