上篇:Spring Security系列之基本原理
项目工程结构如下:
<!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--lombok用来简化实体类--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
完整的pom文件配置
<?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.5.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>org.apache.security</groupId> <artifactId>springsecuritydemo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springsecuritydemo</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-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--lombok用来简化实体类--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> </project>
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
package org.apache.entity; import lombok.Data; @Data public class Users { private Integer id; private String username; private String password; }
package org.apache.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.entity.Users; import org.springframework.stereotype.Repository; @Repository public interface UsersMapper extends BaseMapper<Users> { }
实现它的接口方法
package org.apache.service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.apache.entity.Users; import org.apache.mapper.UsersMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.util.List; @Service("userDetailsService") public class MyUserDetailsService implements UserDetailsService { @Autowired private UsersMapper usersMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //调用usersMapper方法,根据用户名查询数据库 QueryWrapper<Users> wrapper = new QueryWrapper(); // where username=? wrapper.eq("username",username); Users users = usersMapper.selectOne(wrapper); //判断 if(users == null) {//数据库没有用户名,认证失败 throw new UsernameNotFoundException("用户名不存在!"); } List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale"); //从查询数据库返回users对象,得到用户名和密码,返回 return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()),auths); } }
server.port=8111 #mysql数据库连接 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/security?serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123456
package org.apache.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/test") public class TestController { @GetMapping("hello") public String hello() { return "hello security"; } }
package org.apache; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("org.apache.mapper") public class SpringsecuritydemoApplication { public static void main(String[] args) { SpringApplication.run(SpringsecuritydemoApplication.class, args); } }
package org.apache.config; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String password = passwordEncoder.encode("123456"); auth.inMemoryAuthentication().withUser("admin").password(password).roles("admin"); } @Bean PasswordEncoder password() { return new BCryptPasswordEncoder(); } }
package org.apache.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import javax.sql.DataSource; @Configuration public class SecurityConfigTest extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; //注入数据源 @Autowired private DataSource dataSource; //配置对象 @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); //jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); } @Bean PasswordEncoder password() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { //退出 http.logout().logoutUrl("/logout"). logoutSuccessUrl("/test/hello").permitAll(); //配置没有权限访问跳转自定义页面 http.exceptionHandling().accessDeniedPage("/unauth.html"); http.formLogin() //自定义自己编写的登录页面 .loginPage("/on.html") //登录页面设置 .loginProcessingUrl("/user/login") //登录访问路径 .defaultSuccessUrl("/success.html").permitAll() //登录成功之后,跳转路径 .failureUrl("/unauth.html") .and().authorizeRequests() .antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证 //当前登录用户,只有具有admins权限才可以访问这个路径 //1 hasAuthority方法 // .antMatchers("/test/index").hasAuthority("admins") //2 hasAnyAuthority方法 // .antMatchers("/test/index").hasAnyAuthority("admins,manager") //3 hasRole方法 ROLE_sale .antMatchers("/test/index").hasRole("sale") .anyRequest().authenticated() .and().rememberMe().tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(60)//设置有效时长,单位秒 .userDetailsService(userDetailsService); // .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); // .and().csrf().disable(); //关闭csrf防护 } }
启动主程序
http://localhost:8111/test/hello?username=user1&password=123456
http://localhost:8111/test/hello?username=admin&password=123456
这是数据表的用户名与密码都可以,说明测试ok