Spring Security安全框架的介绍和案例使用
Spring Security介绍:
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的事实上的标准。
Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。像所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足定制需求的能力。
Spring Security特性:
-
对身份验证和授权的全面且可扩展的支持
-
保护免受会话固定,点击劫持,跨站点请求伪造等攻击
-
Servlet API集成
-
与Spring Web MVC的可选集成
。。。。。。。
案例Springboot整合SpringSecurity
这里案例是我使用security做的一个demo,仅供参考。
目录结构:
1.pom.xml导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<!--数据库依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
2.application.properties文件
#spring数据源 2.0后默认使用hikari连接池
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/blb_erp?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
#编码问题
server.servlet.encoding.charset=utf-8
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8
##日志级别
logging.level.com.yang.springboot208springsecurity.mapper=debug
#关闭thymeleaf的缓存
#spring.thymeleaf.cache=false
##mybatis-plus mapper xml 映射地址
mybatis-plus.mapper-locations=classpath:mappers/*.xml
##mybatis-plus type-aliases 别名
mybatis-plus.type-aliases-package=com.yang.springboot208springsecurity.entity
3.创建需要的页面
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>登录界面</h3>
<span th:if="${param.error}">账号密码错误</span>
<form action="/toLogin" method="post"><!--/login是security自带的登录接口-->
<!--如果不配置 username和password那名字必须是这两个-->
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<!--解决csrf跨站攻击 不用加 .csrf.disable()-->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<input type="radio" name="remember">记住我<br>
<input type="submit" value="登录">
</form>
</body>
</html>
main.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>主页面</h2>
<h3 th:text="'Hello '+${username}"></h3>
<span th:each="auth : ${authorities}">
[[${auth}]]
</span>
<p><a href="/admin/admin">访问管理员页面</a></p>
<p><a href="/user/user">访问用户页面</a></p>
<form action="/logout" method="post">
<input type="submit" value="退出">
</form>
</body>
</html>
error.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>权限不足请重新登录:<a th:href="@{/toLogin}">登录</a> </h2>
</body>
</html>
user.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>欢迎来到用户界面</h3>
<a href="/logout">返回登录</a>
</body>
</html>
admin.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>欢迎来到管理员界面</h3>
<a href="/logout">返回登录</a>
</body>
</html>
4.实体类
User.java
@Data
public class User {
private long id;
private String username;
private String password;
private String realname;
private long orgId;
private String salt;
private String telephone;
private String icon;
private long state;
private long siteId;
}
Role.java
/**
* 角色实体类
*/
@Data
public class Role {
private long id;
private String name;
private String description;
private long siteId;
}
Permission.java
/**
* 权限实体类
*/
@Data
public class Permission {
private long id;
private String name;
private String description;
private String url;
private long pid;
private String icon;
private long sort;
}
### 5. Mapper层:
UserMapper.java
public interface UserMapper extends BaseMapper<User> {
List<Role> selectRoleByUsername(String username);
List<Permission> selectPermissionByUsername(String username);
}
UserMapper.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.yang.springboot208springsecurity.mapper.UserMapper">
<select id="selectRoleByUsername" parameterType="String" resultType="Role">
select r.* FROM
user u JOIN user_role ur ON u.id=ur.user_id
JOIN role r ON r.id=ur.role_id
where u.username=#{username}
</select>
<select id="selectPermissionByUsername" parameterType="String" resultType="Permission">
select p.* FROM
user u JOIN user_role ur ON u.id=ur.user_id
JOIN role r ON r.id=ur.role_id
JOIN role_permission rp on rp.role_id=r.id
JOIN permission p ON p.id=rp.fun_id
where u.username=#{username}
</select>
</mapper>
6.service层
UserService接口:
public interface UserService extends IService<User> {
}
UserServiceImpl实现类:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
UserDetailsServiceImpl:
/**
* 自定义登录权限逻辑实现
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
/*获取用户信息通过用户名*/
User user = userMapper.selectOne(new QueryWrapper<User>().lambda().eq(User::getUsername, s));
if (user==null){
throw new UsernameNotFoundException("用户不存在");
}
/*查询用户角色*/
List<Role> roles = userMapper.selectRoleByUsername(s);
/*查询用户权限*/
List<Permission> permissions = userMapper.selectPermissionByUsername(s);
StringBuilder string = new StringBuilder();
/*将权限和角色封装成一个字符串*/
roles.forEach(role->{string.append("ROLE_"+role.getName()+",");});
permissions.forEach(permission -> {string.append(permission.getName()+",");});
if(string.length()>0){
string.deleteCharAt(string.length()-1);
}
/*创建security的User包装用户名密码和权限*/
org.springframework.security.core.userdetails.User user1 = new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
AuthorityUtils.commaSeparatedStringToAuthorityList(string.toString()));
return user1;
}
}
7.配置类
RememberMeConfig.java
/**
* 记住我配置
*/
@Configuration
public class RememberMeConfig {
/*注入数据源*/
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//创建记录用户登录的表persistent_logins 如果存在会报错
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
SecurityConfig.java:
/**
* websecurity配置
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/*注入自定义用户验证*/
@Resource
private UserDetailsService userDetailsService;
@Autowired
private PersistentTokenRepository persistentTokenRepository;
/**
* security登录验证必须经过加密处理 passwordEncoder接口进行面没加密
* @return 返回密码编码器
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 认证规则 用户名密码和角色 (用户名和密码可以利用数据库来验证)
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/*//定义用户名和密码和用户权限
auth.inMemoryAuthentication()
//第一个用户配置 角色设置
.withUser("yang").password(new BCryptPasswordEncoder().encode("123456")).roles("user","admin")
.and()
//第二个用户配置 角色设置
.withUser("wang").password(new BCryptPasswordEncoder().encode("123456")).roles("user");
*/
/*使用自定义用户验证和授权*/
auth.userDetailsService(userDetailsService);
}
/**
* 配置页面的授权
* @param http
* @throws Exception
*/
@Override //定制权限规则
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //配置页面的授权
.antMatchers("/login","/toLogin","/js/**","/**/*.css","/hello").permitAll() //前面的所有url不用验证
.antMatchers("/admin/**").hasRole("管理员") //该路径对应的角色
.antMatchers("/user/**").hasAuthority("销售管理") //该路径对应的权限也可以放角色 ROLE
.anyRequest().authenticated() //除了前面的url 都要验证
.and()
.formLogin()//登录相关配置
.loginPage("/toLogin") //失败后跳转的url
.successForwardUrl("/toMain")//登录成功后跳转的访问url
.usernameParameter("username") //用户名参数名
.passwordParameter("password") //密码参数名
.and()
.logout().logoutUrl("/logout")//登录退出的访问url 必须是post请求
.logoutSuccessUrl("/toLogin") //退出页面
.and()
.rememberMe()
.rememberMeParameter("remember") //表单参数名
.tokenRepository(persistentTokenRepository) //设置数据源
.tokenValiditySeconds(60); //设置记住生命周期
//.and()
//.csrf().disable();//生产环境下禁用跨域攻击防御,不然登录提交失败
//如果开启需要请求中携带_csrf=tokenxxxx否者登录会失败
}
}
8.Controller层:
@Controller
public class UserController {
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("/toMain")
public String toMain(Model model){
/*获得当前用户的验证信息*/
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
if (principal instanceof org.springframework.security.core.userdetails.User){
User user = (User) principal;
model.addAttribute("username",user.getUsername());
model.addAttribute("authorities",user.getAuthorities());
}
return "main";
}
@RequestMapping("/admin/admin")
public String toAdmin(){
return "admin/admin";
}
@RequestMapping("/user/user")
public String toUser(){
return "user/user";
}
@RequestMapping("/logout")
public String toLogout(){
return "login";
}
}
9.springboot主启动类
@MapperScan(basePackages = "com.yang.springboot208springsecurity.mapper")
@SpringBootApplication
public class Springboot208SpringsecurityApplication {
public static void main(String[] args) { SpringApplication.run(Springboot208SpringsecurityApplication.class, args);
}
}
10.运行结果:
不同的用户有不同的权限有的页面是进不去的: