springboot集成springsecurity
【摘要】 springsecurity作为一款成熟的安全认证框架,对于简单的系统架构,有着集成即用,简单有效的优点,因此做一个简单的springboot集成springsecurity的代码演示,总结归纳。源代码文件所有文件展示如下依赖组件pom.xml<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.o...
springsecurity作为一款成熟的安全认证框架,对于简单的系统架构,有着集成即用,简单有效的优点,因此做一个简单的springboot集成springsecurity的代码演示,总结归纳。
源代码文件
所有文件展示如下
依赖组件pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>security</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--mybatis-plus 是自己开发的,非官方的!-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
applicaiton.properties配置
spring.security.user.name=root
spring.security.user.password=123456
spring.datasource.driver-class-name: com.mysql.cj.jdbc.Driver
spring.datasource.url: jdbc:mysql://localhost:3306/sans_security?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
spring.datasource.username: root
spring.datasource.password: 123456
SecurityConfig配置文件
/**
* security配置
* 配置登录登出,权限认证,页面跳转,认证失败或者成功处理,部分认证开启或者关闭
*
* @author object
* @since 2022年6月25日
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
@Autowired
private PersistentTokenRepository tokenRepository;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
// 设置加密函数
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 设置token仓库,持久化数据。用于rememberMe功能,识别用户登录状态
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
// 自定义配置,登录登出接口,权限开关开启关闭等
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 配置没有权限访问跳转自定义页面
httpSecurity.exceptionHandling().accessDeniedPage("/403.html");
httpSecurity.formLogin() // 自定义编写的登录页面
.loginPage("/login.html") // 登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径
.defaultSuccessUrl("/success.html").permitAll() // 登录后默认跳转路径
.and()
.logout() // 自定义编写登出页面
.logoutUrl("/logout") // 登出url
.logoutSuccessUrl("/login.html").permitAll() // 跳转地址
.and().authorizeRequests() // 请求认证
.antMatchers("/hello").permitAll()
// .antMatchers("/", "/user/login").permitAll() // 不需要认证的路径
// .antMatchers("/test/addUser").hasAuthority("addUser")
.antMatchers("/hello2").hasAnyAuthority("addUser,finadAll")
.antMatchers("/hello3").hasRole("admin")
.antMatchers("/hello4").hasRole("admin4")
.antMatchers("/test/hello").hasAnyRole("admin")
.anyRequest().authenticated() // 任何请求都需要认证
.and()
.rememberMe() // rememberMe,记住我功能
.tokenRepository(tokenRepository) // token仓库
.tokenValiditySeconds(600) // token有效时间
.userDetailsService(userDetailsService)
.and().csrf().disable(); // 关闭csrf校验
}
}
MyUserDetailsService.java
/**
* 自定义userDetailsService, 用于登录时校验用户和密码,设置用户权限等
*
* @author object
* @since 2022年6月25日
*/
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
// 登录时,校验用户密码,查询用户权限
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
QueryWrapper<UserEntity> userEntityQueryWrapper = new QueryWrapper<>();
userEntityQueryWrapper.eq("username", userName);
UserEntity userEntity = usersMapper.selectOne(userEntityQueryWrapper);
if (userEntity == null) {
throw new UsernameNotFoundException("用户不存在");
}
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("finadAll,ROLE_admin,ROLE_admin5,hello6");
return new User(userEntity.getUsername(), new BCryptPasswordEncoder().encode(userEntity.getPassword()), auths);
}
}
用户相关的代码
UserEntity
/**
* 用户实体
*
* @author object
* @since 2022年6月25日
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("users")
public class UserEntity {
private Integer id;
private String username;
private String password;
}
UserMapper
/**
* 用户mapper
*
* @author object
* @since 2022年6月25日
*/
@Mapper
public interface UsersMapper extends BaseMapper<UserEntity> {
}
User
/**
* springsecurity提供的用户model,用于认证过程中储存用户信息和认证
* springsecurity也提供了默认的org.springframework.security.core.userdetails.User实体,
* 项目一般使用自己的,可以定义一些自定义的字段
*
* @author object
* @since 2022年6月25日
*/
public class User implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = 530L; // 反序列化
private static final Log LOG = LogFactory.getLog(User.class);
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
public User(String username, String password,
Collection<? extends GrantedAuthority> authorities) {
this(username, password, true, true, true, true, authorities);
}
public User(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
if (((username == null) || "".equals(username)) || (password == null)) {
throw new IllegalArgumentException(
"Cannot pass null or empty values to constructor");
}
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}
private static SortedSet<GrantedAuthority> sortAuthorities(
Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(
new AuthorityComparator());
for (GrantedAuthority grantedAuthority : authorities) {
Assert.notNull(grantedAuthority,
"GrantedAuthority list cannot contain any null elements");
sortedAuthorities.add(grantedAuthority);
}
return sortedAuthorities;
}
private static class AuthorityComparator implements Comparator<GrantedAuthority>,
Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
public int compare(GrantedAuthority g1, GrantedAuthority g2) {
// Neither should ever be null as each entry is checked before adding it to
// the set.
// If the authority is null, it is a custom authority and should precede
// others.
if (g2.getAuthority() == null) {
return -1;
}
if (g1.getAuthority() == null) {
return 1;
}
return g1.getAuthority().compareTo(g2.getAuthority());
}
}
@Override
public void eraseCredentials() {
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
}
LoginApi
/**
* 测试接口
*
* @author object
* @since 2022年6月25日
*/
@RestController
public class LoginApi {
// 测试系统是否正常能访问
@RequestMapping(value = "/hello",method = RequestMethod.GET)
public String hello(){
return "hello security";
}
// 测试权限 .antMatchers("/hello2").hasAnyAuthority("addUser,finadAll")
@RequestMapping(value = "/hello2",method = RequestMethod.GET)
public String hello2(){
return "hello2 security";
}
// 测试权限 .antMatchers("/hello3").hasRole("admin")
@RequestMapping(value = "/hello3",method = RequestMethod.GET)
public String hello3(){
return "hello3 security";
}
// 测试权限 .antMatchers("/hello4").hasRole("admin4")
@RequestMapping(value = "/hello4",method = RequestMethod.GET)
public String hello4(){
return "hello4 security";
}
// 测试权限 .antMatchers("/test/hello").hasAnyRole("admin")
@RequestMapping(value = "/hello5",method = RequestMethod.GET)
@Secured({"ROLE_admin5"})
public String hello5(){
return "hello5 security";
}
// 测试权限 @PreAuthorize("hasAuthority('hello6')")
@RequestMapping(value = "/hello6",method = RequestMethod.GET)
@PreFilter("filterObject.id%2==0") // 调用前过滤数据
@PreAuthorize("hasAuthority('hello6')")
public String hello6(){
return "hello6 security";
}
// 测试权限 @PostAuthorize("hasAuthority('hello7')")
@RequestMapping(value = "/hello7",method = RequestMethod.GET)
@PostAuthorize("hasAuthority('hello7')") // 调用后校验
@PostFilter("filterObject.username == 'admin'") // 校验权限后,过滤数据
public String hello7(){
return "hello7 security";
}
}
前端静态页面
403.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>403</title>
</head>
<body>
<h1>对不起,您没有访问权限!</h1>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
<!--注意:页面提交方式必须为 post 请求,用户名,密码必须为username,password
可以通过 usernameParameter()和 passwordParameter()方法修改默认配置-->
用户名:<input type="text" name="username">
<br/>
密码:<input type="text" name="password">
<br/>
<input type="submit" value="login">
<!--name 属性值必须位 remember-me.不能改为其他值-->
记住我:<input type="checkbox" name="remember-me" title="记住密码"/><br/>
</form>
</body>
</html>
success.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>success</title>
</head>
<body>
登录成功<br> <a href="/logout">退出</a>
</body>
</html>
主入口
SecurityApplication.java
@SpringBootApplication
@MapperScan("com.example.security.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) // 开启注解权限方式,允许先查后校验以及查询过滤
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}
部分关键说明
SecurityConfig.java
springsecurity关键配置文件,涉及各种详细配置,登录登出,接口配置,白名单配置,认证失败策略配置,csrf开启关闭等,是非常重要的配置文件,文中只写了一些简单的配置,像失败策略,成功策略等都没有配置。
User.java
springsecurity认证过程中,用于存储用户信息的载体。以及认证通过后,再次请求时,获取请求的用户信息。
例如获取用户信息的方法:
SecurityContextHolder.getContext().getAuthentication()
request.getUserPrincipal()
MyUserDetailsService.java
重写登录时,校验用户是否存在和密码准确的试验代码,同时封装User用户信息并返回。
接口权限配置
一般接口的权限配置,不会像代码演示中一样写死,一个接口一个接口配置,需要的角色权限。一般都会配置在数据库中,动态的变更。除非系统接口较少,用uri加以区分。
记住我功能
记住我功能的需要在securityConfig中开启,并配置token工厂。如果需要持久化到数据库,则还需要创建表。sql语句在JdbcTokenReposityoryImpl中直接copy即可。
org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl
/** Default SQL for creating the database table to store the tokens */
public static final String CREATE_TABLE_SQL = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, "
+ "token varchar(64) not null, last_used timestamp not null)";
新建表后,数据会自动填充到改表。
最后,需要使用rememberMe功能。需要在login的时候,传入remember-me参数,login.html中已设置改值。这样才能完美使用记住我,这样即使浏览器关闭,只要cookie中的remererber-me属性还在,就可以继续访问系统无需认证。
记住我:<input type="checkbox" name="remember-me" title="记住密码"/><br/>
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)