赞
踩
SpringBoot 可以给我们省去很多的配置文件和工程搭建,快速导入依赖包,并且SpringBoot也是一个市面上常用的框架,可以整合市面上大部分其他框架,使用开发效率很高。
我们创建一个空的工程然后引入maven依赖来搭建springboot框架。
(1)创建一个空的maven工程,引入依赖。
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mcs.security</groupId> <artifactId>springboot-springsecurity</artifactId> <version>1.0-SNAPSHOT</version> <parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.1.3.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.complier.source>1.8</maven.complier.source> <maven.complier.target>1.8</maven.complier.target> </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> </dependency> <!-- 数据库依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- jsp 依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.0</version> </dependency> </dependencies> <build> <!-- 改为你的项目名称 --> <finalName>springboot-springsecurity</finalName> <pluginManagement> <plugins> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.0.0</version> </plugin> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <!--<configuration> <path>/shiro</path> <port>8080</port> <uriEncoding>UTF-8</uriEncoding> <url>http://localhost:8080/shiro</url> <server>Tomcat7</server> </configuration>--> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <configuration> <encoding>utf-8</encoding> <useDefaultDelimiters>true</useDefaultDelimiters> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>**/*</include> </includes> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </configuration> </plugin> </plugins> </pluginManagement> </build> </project>
(2)spring容器配置
springboot工程会自动扫描启动类所在包下的所有Bean,加载到spring容器。
在resource下创建application.properties
我这里默认把后面用到的配置也贴了上来
server.port=8080 server.servlet.context-path=/springboot-springsecurity spring.application.name=springboot-springsecurity spring.mvc.view.prefix=/WEB-INF/views/ spring.mvc.view.suffix=.jsp # 数据库配置 spring.datasource.url=jdbc:mysql://localhost:3306/user_db spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver # 设置session超时时间 #server.servlet.session.timeout=10s
(3) SpringBoot启动类
启动类要创建在和所有子包同级目录
@SpringBootApplication
@Configuration
public class SecuritySpringBootApp {
public static void main(String[] args) {
SpringApplication.run(SecuritySpringBootApp.class, args);
}
}
(4)servlet Context配置
相当于我们配置ssm框架时的springMVC主配置文件
这里配置不像我们上次还需要@EnableWebMvc与@ComponentScan注解,SpringBoot的自动装配机制会为我们配置。
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 默认url根路径跳转到mvc解析地址下的login
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:/login-view");
registry.addViewController("/login-view").setViewName("login");
}
}
视图解析器配置到application.properties文件中,比在配置文件中配置方便多了
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
(5)SpringSecurity配置
省去了@EnableWebSecurity配置
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // 配置模拟用户信息 @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build()); manager.createUser(User.withUsername("lisi").password("123").authorities("p2").build()); return manager; } // 密码编码器 @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } // 配置安全拦截机制 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/r/r1").hasAuthority("p1") .antMatchers("/r/r2").hasAuthority("p2") .antMatchers("/r/**").authenticated() // 拦截/r/**请求 .anyRequest().permitAll() // 其他请求正常方行 .and() .formLogin().successForwardUrl("/login-success"); } }
(6)测试
添加controller配置
@RestController public class loginController { @RequestMapping(value = "/login-success", produces = {"text/plain;charset=utf-8"}) public String loginSuccess() { return "登录成功"; } @RequestMapping(value = "/r/r1", produces = {"text/plain;charset=utf-8"}) public String r1() { return "访问资源r1"; } @RequestMapping(value = "/r/r2", produces = {"text/plain;charset=utf-8"}) public String r2() { return "访问资源r2"; } }
springSecurity认证流程
1、用户提交用户名密码会先经过UsernamePasswordAuthentionFilter
过滤器获取,封装为Authentication。
2、然后将Authentication提交到认证管理器AuthenticationMangere
认证,认证就是一个鉴定用户身份的过程,认证成功后会返回一个Authentication对象,里面包含了身份信息,这个身份信息是一个Object,会被强转成UserDetails
对象。
3、它的认证过程是什么样的呢?认证管理器将认证托管给AuthenticationProvider
来认证,而AuthenticationProvider中存放了多种认证方式,比如短信认证,密码认证啥的,密码认证就是使用图上的DaoAuthenticationProvider
来认证。
4、DaoAuthenticationProvider内部做了什么呢?上面提到最后返回的对象是UserDetails,从图中也可看出来,这个过程是DaoAuthenticationProvider交给UserDetailService
来办,UserDetailService负责从数据库加载数据,用其loadUserByUsername
方法查询数据,返回UserDetails。
5、DaoAuthenticationProvider收到了UserDetailService,就会进行密码对比,最后封装成Authentication
对象返回。
6、图中最后一步呢,就是将已经登入的用户数据保存。
通过了上面的分析,大概知道了UserDetailService是来干嘛的,它就是简单的获取用户数据,那么我们就可以通过自定义实现该接口,重写loadUserByUsername方法来从指定的数据库查询用户。
前面我们是在springSecurity中模拟了用户信息,我们把前面的模拟数据注释掉,我们先实现下该接口,在loadUserByUsername中返回一个用户(相当于已经查到了),回来再改为数据库查询。
注释掉springSecurity配置文件中的模拟数据
// 配置模拟用户信息
/* @Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("123").authorities("p2").build());
return manager;
}*/
在service包中实现该接口
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//登录账号
System.out.println("username="+username);
//根据账号去数据库查询...
//这里暂时使用静态数据
UserDetails userDetails =
User.withUsername(username).password("123").authorities("p1").build();
return userDetails;
}
}
测试
在return userDeatils出插个断点,以Debug方式运行。
连接数据库的第一步首先得有数据库吧(说话等于放屁)
1、建表
很简单,自己建一遍就好。
2、引入依赖,上面的pom.xml是已经引入了该项目所需要的全部依赖。
3、在application.properties配置数据库信息
上面配置文件已经配置好了,不重复贴了
4、定义实体类
@Data
public class UserDto {
private String id;
private String username;
private String password;
private String fullname;
private String mobile;
}
5、定义永久层Dao
因为是查询数据简单可以直接在dao里写方法,不过考虑到以后可以整合Mybatis我就使用了三层体系结构来查询,顺便把授权表所需要的实体类及方法也贴进去。
UserDao.java
@Repository
public interface UserDao {
// 根据账户查询
public UserDto getUserByUsername(String username);
// 根据id查询权限
public List<String> findPermissionByUserId(String userId);
}
UserDaoImpl.java
@Repository public class UserDaoImpl implements UserDao { @Autowired JdbcTemplate jdbcTemplate; @Override public UserDto getUserByUsername(String username) { String sql = "select id,username,password,fullname from t_user where username = ?"; List<UserDto> list = jdbcTemplate.query(sql, new Object[]{username}, new BeanPropertyRowMapper<>(UserDto.class)); if (list == null) { return null; } return list.get(0); } @Override public List<String> findPermissionByUserId(String userId) { // 根据用户id查询到其所拥有的权限信息 String sql = "select * from t_permission where id in \n" + "(select permission_id from t_role_permission where role_id in \n" + "(select role_id from t_user_role where user_id = ?))"; List<PermissionDto> list = jdbcTemplate.query(sql, new Object[]{userId}, new BeanPropertyRowMapper<>(PermissionDto.class)); // 从权限信息中剥离出权限字段 List<String> permissions = new ArrayList<String>(list.size()); list.iterator().forEachRemaining(c->permissions.add(c.getCode())); return permissions; } }
UserDaoService.java
@Service
public interface UserDaoService {
// 根据账户查询
public UserDto getUserByUsername(String username);
// 根据id查询权限
public List<String> findPermissionByUserId(String userId);
}
UserDaoSerivceImpl.java
@Service
public class UserDaoServiceImpl implements UserDaoService {
@Autowired
UserDao userDao;
@Override
public UserDto getUserByUsername(String username) {
return userDao.getUserByUsername(username);
}
@Override
public List<String> findPermissionByUserId(String userId) {
return userDao.findPermissionByUserId(userId);
}
}
6、修改自定义UserDetailService
将原来的模拟数据删了,直接从数据库访问数据,注意由于权限信息我们还没有在数据库建立相关表,所以权限暂时使用静态数据,即.authorities("p1")
@Service public class SpringDataUserDetailsService implements UserDetailsService { @Autowired UserDaoService userDaoService; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { //根据账号去数据库查询 UserDto userByUsername = userDaoService.getUserByUsername(s); if(userByUsername == null){ return null; } UserDetails userDetails = User.withUsername(userByUsername .getFullname()).password(userByUsername .getPassword()).authorities("p1").build(); return userDetails; } }
7、测试
用数据信息登录试试,内容较多不贴图了。
DaoAuthenticationiProvider通过PasswordEncoder接口中的matches方法进行密码对比,SpringSecurity提供了很多内置的PasswordEncoder,直接在其配置文件中声明即可。
在前边springSecurity配置中使用了如下密码加密方式
// 密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
这是采用字符串匹配方式来进行比较并没有进行加密,只是简单比较了密码。
使用BCrypePasswordEncoder
进行密码加密
1、一般密码是在注册时提交到后台再进行加密存到数据库,但我们并没有注册功能,所以就添加个测试类,将存的密码转换为BCrypePasswordEncoder格式,然后手动存到数据库。
在test中创建测试方法
@RunWith(SpringRunner.class)
public class BcryTest {
@Test
public void test1() {
// 对原始密码进行加密
String password = BCrypt.hashpw("123", BCrypt.gensalt());
System.out.println(password);
}
}
将上面的密码存到我们数据就可以了
2、配置加密方式
在security配置文件中配置如下
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
3、测试能否登录
前面我们使用的都是springSecurity自带的登录及退出界面,下面我们自定义登录界面。
1、创建登录页面
在视图解析器配置的指定地址下创建,webapp是和resource同级目录。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户登录</title>
</head>
<body>
<form action="login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
2、在webConfig.java中配置地址
上面应该贴过了
// 默认url根路径跳转到mvc解析地址下的login
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:/login-view");
registry.addViewController("/login-view").setViewName("login");
}
3、配置security
springsecurity为防止CSRF(Cross-siterequestforgery跨站请求伪造)的发生,限制了除了get以外的大多数方法,我们屏蔽CSRF即可。
// 配置安全拦截机制 @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() //屏蔽CSRF控制,即springsecurity不再限制CSRF .authorizeRequests() .antMatchers("/r/r1").hasAuthority("p1") .antMatchers("/r/r2").hasAuthority("p2") .antMatchers("/r/**").authenticated() // 拦截/r/**请求 .anyRequest().permitAll() // 其他请求正常方行 .and() .formLogin() // 允许表单登录 .loginPage("/login-view") // 指定自己的登录页面,已经将 ‘/’ 路径跳到试图解析器配置路径下的 ‘login’ .loginProcessingUrl("/login") // 用户名密码提交的目的地址 .successForwardUrl("/login-success") // 指定登录成功后跳转的url .permitAll() // .formLogin().permitAll()允许所有用户访问登录页面 .and() .sessionManagement() // 会话控制 .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login-view?logout"); }
会话功能即我们在访问资源时显示出是谁在访问资源,我们在controller中定义了两个资源,r1,r2。
在分析认证流程中提到,最后一步会将登录的用户数据存在SecurityContextHolder上下文中,我们可通过它来访问用户信息,显示该用户访问了该资源。
修改过后的controller
@RestController public class loginController { @RequestMapping(value = "/login-success", produces = {"text/plain;charset=utf-8"}) public String loginSuccess() { // 获取用户信息显示出来 return getUsername()+"登录成功"; } @RequestMapping(value = "/r/r1", produces = {"text/plain;charset=utf-8"}) public String r1() { return getUsername()+"访问资源r1"; } @RequestMapping(value = "/r/r2", produces = {"text/plain;charset=utf-8"}) public String r2() { return getUsername()+"访问资源r2"; } // 获取用户姓名 private String getUsername() { // Spring Security在认证完成后通过 // SecurityContextHolder.getContext().setAuthentication()方法将Authentication保存在上下文 // 所以我们可以通过访问上下文中的Authentication来获取用户信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); String username = null; if(principal instanceof org.springframework.security.core.userdetails.UserDetails) { username = ((UserDetails) principal).getUsername(); } else { username = principal.toString(); } return username; } }
再访问资源时即可显示相关用户信息
测试
登录张三账户,访问资源r1
1、建表
授权功能需要访问到用户的权限信息,第一步从表中筛选出当前用户属于哪个角色,然后该角色拥有哪些权限。
角色表t_role
用户角色表t_user_role
权限表t_permission
角色权限表t_role_permission
2、在dao接口中创建查询权限方法
相关方法代码已经在第四部连接数据创建三层架构时贴上去了(太懒了)
3、将UserDetailService改为动态获取权限
@Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { UserDto userByUsername = userDaoService.getUserByUsername(s); if (userByUsername == null) { return null; } // 查询权限字段信息 String userId = userByUsername.getId(); List<String> permissions = userDaoService.findPermissionByUserId(userId); // 因为返回的UserDetails创建权限是变长度的String数组,转换一下 String[] per = new String[permissions.size()]; permissions.toArray(per); // 模拟查询数据库 UserDetails p1 = User.withUsername(userByUsername.getUsername()).password(userByUsername.getPassword()).authorities(per).build(); return p1; }
还没写呢,下次一定
博客是在学习黑马程序员SpringSecurity时写的,有不会的可在评论区留言交流(手动呲牙)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。