Spring Security
1.认识Spring Security
什么是Spring Security ?顾名思义,它是一个负责为Java程序提供身份验证和授权的Spring框架,与所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以轻松地扩展以满足自定义要求。Spring Security的核心目标是处理身份验证(Authentication)和授权(Authorization)两大安全关键领域
- 验证 : 认证是确定用户身份的过程,即验证用户声称的身份是否真实。
- 授权 :授权是在认证之后进行的,目的是决定已经认证的用户是否有权限访问某个资源或执行某个操作。
1.1 验证的核心步骤
- 用户凭证收集 :用户通过表单登录、API请求等方式提供用户名和密码。
- 凭证验证:Spring Security使用配置的认证管理器(AuthenticationManager)来验证这些凭证。认证管理器背后可能是数据库查询等。
- 创建AuthenticationManager对象: 一旦凭证被验证为有效,Spring Security会创建一个表示该用户的
Authentication
对象,其中包含用户的身份信息(通常是用户名)和授予的权限。 - 安全上下文设置: 验证通过的
Authentication
对象会被放置到安全上下文(SecurityContextHolder),使得整个应用可以在需要时访问当前认证用户的信息。
1.2 授权的核心步骤
- **访问决策:**授权的核心是访问决策,由访问决策管理器(AccessDecisionManager)执行。它根据配置的策略评估用户是否有权访问特定资源。
- **Web授权:**通过
FilterSecurityInterceptor
,Spring Security可以基于URL路径对HTTP请求进行拦截,根据配置的访问规则(如antMatchers
)判断是否允许访问。 - **方法授权:**通过
MethodSecurityInterceptor
,可以在方法级别应用安全约束。开发者可以使用注解(如@PreAuthorize
、@PostAuthorize
)来标记方法,基于表达式语言(如SpEL)来定义复杂的授权规则。 - **角色与权限:**权限通常与用户角色关联,Spring Security支持基于角色的访问控制(RBAC),允许开发者定义角色,并为角色分配特定权限。
1.3 实现流程
认证会先发生,就比如说我们想要在一个管理后台进行对某个员工的操作,首先我们就必须先要登录,保证用户的真实性和合法性,其次就是授权,登录成功后就必须要有一个对员工进行操作的权限或资格,这个就是授权,保证被认证过的用户只能访问其被允许的资源。
2. Spring Boot整合Spring Security
2.1 使用Mybatis-Plus代码生成器生成基本代码
mybatis-plus有一款代码生成器,基于对代码生成器的基本配置,可以实现自动生成一些基本代码,如Entity、Mapper、Controller等,大大加快了开发效率。
-
首先导入代码生成器所需要的依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.0</version> </dependency>
-
然后创建Genarator类,编写如下代码:
-
public class Genarator { public static void main(String[] args) { //1. 全局配置 GlobalConfig config = new GlobalConfig(); config.setAuthor("paynez") .setOutputDir("C:/Users/17868/Desktop/src/main/java") .setFileOverride(true) .setIdType(IdType.AUTO) .setDateType(DateType.ONLY_DATE) .setServiceName("%sService") .setEntityName("%s") .setBaseResultMap(true) .setActiveRecord(false) .setBaseColumnList(true); //2. 数据源配置 DataSourceConfig dsConfig = new DataSourceConfig(); // 设置数据库类型 dsConfig.setDbType(DbType.MYSQL) .setDriverName("com.mysql.cj.jdbc.Driver") .setUrl("jdbc:mysql:///security-demo") .setUsername("root") .setPassword("root"); //3. 策略配置globalConfiguration中 StrategyConfig stConfig = new StrategyConfig(); //全局⼤写命名 stConfig.setTablePrefix("t_"); stConfig.setCapitalMode(true) .setNaming(NamingStrategy.underline_to_camel) .setEntityLombokModel(true) .setRestControllerStyle(true) // .setInclude("表名") .setInclude(scanner("表名,多个英⽂逗号分割").split(",")); PackageConfig pkConfig = new PackageConfig(); pkConfig.setParent("com.zjy.demo") .setMapper("mapper") .setService("service") .setController("controller") .setEntity("entity") .setXml("mapper"); //5. 整合配置 AutoGenerator ag = new AutoGenerator(); ag.setGlobalConfig(config) .setDataSource(dsConfig) .setStrategy(stConfig) .setPackageInfo(pkConfig); //6. 执⾏操作 ag.execute(); System.out.println("======= 相关代码⽣成完毕 ========"); } /** * <p> * 读取控制台内容 * </p> */ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输⼊" + tip + ": "); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotBlank(ipt)) { return ipt; } } throw new MybatisPlusException("请输⼊正确的" + tip + "! "); } }
-
然后执行程序,在控制台输入数据库中的表名 则会自动生成代码。
2.2 创建Spring Boot 工程
- 在一个空项目里创建Spring Boot模块 选择如下依赖:
- 并且手动导入Mybatis-plus和fastjson的Maven坐标
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.47</version>
</dependency>
- 然后将代码生成器生成的一些基础代码的包粘贴到项目路径下,这样一个基础的开发环境就创建完成了。
3.使用Spring Security实现验证和授权
3.1 验证
3.1.1 使用自定义的登录页面
当我们创建好了工程之后启动工程访问localhost:8080,Spring Security会自动弹出来一个登录页面,默认用户名是user,密码则是提示在控制台的一条很长的字符串。输入用户名和密码之后点击登录,这样就跳转到了我们项目的首页。如何才能不使用Spring Security为我们提供的默认登录页呢? (我们自己手动创建一些Html文件简单实现功能)。
- 首页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>欢迎来到首页</h1>
<ul>
<li>
<a th:href="@{/emp/login}">登录</a><br>
<a th:href="@{/emp/show_emp_all}">查看员工</a><br>
<a th:href="@{/cw/show_cw_all}">查看财务</a><br>
<a th:href="@{/order/show_order_all}">查看订单</a>
</li>
</ul>
</body>
</html>
- 登录页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<form th:action="@{/emp/do_login}" method="post">
<input type="text" name="username" placeholder="用户名">
<br>
<input type="password" name="password" placeholder="密码">
<input type="submit" value="登录">
</form>
</body>
</html>
3.1.2 模拟验证功能
- 创建完首页和登录页之后我们创建SpringSecurity的配置类继承WebSecurityConfigurerAdapter,目的是为了定义SpringSecurity安全配置。
- 重写void configure(HttpSecurity http)方法,这样我们可以直接在首页中点击登录使用我们自定义的登陆页面了。
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置基于URL的访问控制。
// 这个方法允许你定义哪些URL需要认证
// 哪些URL可以匿名访问,以及哪些URL需要特定的角色或权限。
http.authorizeRequests()
//放行根目录 这样进首页就不用security的默认登录了
.antMatchers("/","/emp/login","/emp/do_login").permitAll()
//使用hasRole来指定某个角色可以访问某个URL 实现的就是security的授权
.antMatchers("/emp/**").hasRole("ADMIN")
.antMatchers("/cw/**").hasRole("CW_MANAGER")
.antMatchers("/order/**").hasRole("ORDER_MANAGER");
// 开启表单登录
http.formLogin()
//设置登录页面
.loginPage("/emp/login")
//设置登录处理URL
.loginProcessingUrl("/emp/do_login")
//设置用户名参数
.usernameParameter("username")
//设置密码参数
.passwordParameter("password")
//验证失败返回的页面
.failureUrl("/emp/login?error="+"true")
.and()
//对请求授权
.authorizeRequests()
//所有请求
.anyRequest()
//都需要认证
.authenticated()
.and()
//禁止跨域请求
.csrf().disable();
}
- 然后重写void configure(AuthenticationManagerBuilder auth)方法 目的是配置Spring Security的认证信息 我们现在可以在这里定义一个用户来模拟实现认证功能。使用auth.inMemoryAuthentication()方法去模拟创建对象,实现验证功能。至于下面的auth.userDetailsService(userService)就是我们使用自定义的service去从数据库中查询数据进行登录验证。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication()
// //创建一个对象 并设置用户名和密码以及角色
// .withUser("admin").password("{noop}123").roles("ADMIN").and()
// .withUser("lzl").password("{noop}123").roles("CW_MANAGER").and()
// .withUser("jl").password("{noop}123").roles("ORDER_MANAGER");
// 使用自定义的UserService来获取用户信息
auth.userDetailsService(userService);
}
3.1.3 使用数据库数据进行登录验证
-
首先编写Mapper接口继承Mybatis-plus的BaseMapper接口
@Mapper public interface UserMapper extends BaseMapper<User> { List<User> selectUserAll(); User selectUserByUsername(String username); }
-
编写Service接口和ServiceImpl实现类 Service实现类继承UserDetailsService
public interface UserService extends UserDetailsService { } @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; private final static Logger log = LoggerFactory.getLogger(UserServiceImpl.class); /** * 根据传入的username实现用户登录功能 @param username * @return @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { com.zjy.demo.entity.User loginUser = userMapper.selectUserByUsername(username); log.info("您要登录的用户名:{} " , username); User user = new User(loginUser.getUsername(),"{noop}"+loginUser.getPassword(),getAuthorty(loginUser)); return user; } private List<SimpleGrantedAuthority> getAuthorty(com.zjy.demo.entity.User loginUser){ //权限集合 List<SimpleGrantedAuthority> authorities = new ArrayList<>(); //遍历登录用户的角色集合 List<Role> roles = loginUser.getRoles(); roles.forEach(role -> { //把数据库中的角色名加上ROLE前缀,作为权限添加到权限集合中 authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName())); }); //手动给集合中添加权限 // authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); // authorities.add(new SimpleGrantedAuthority("ROLE_CW_MANAGER")); // authorities.add(new SimpleGrantedAuthority("ROLE_ORDER_MANAGER")); return authorities; } }
-
其中重写UserDetails loadUserByUsername(String username)方法,根据username查询数据库的user对象,使用springframework.security.core.userdetails.User的构造器将查询出来的结果封装到这个对象中并且返回。AuthenticationManager 会自动这样 auth.userDetailsService(userService)进行身份验证,就实现了使用数据库的数据进行验证的功能。
public interface UserService extends UserDetailsService {
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
private final static Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
/**
* 根据传入的username实现用户登录功能
@param username
* @return
@throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
com.zjy.demo.entity.User loginUser = userMapper.selectUserByUsername(username);
log.info("您要登录的用户名:{} " , username);
User user = new User(loginUser.getUsername(),"{noop}"+loginUser.getPassword(),getAuthorty(loginUser));
return user;
}
private List<SimpleGrantedAuthority> getAuthorty(com.zjy.demo.entity.User loginUser){
//权限集合
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
//遍历登录用户的角色集合
List<Role> roles = loginUser.getRoles();
roles.forEach(role -> {
//把数据库中的角色名加上ROLE前缀,作为权限添加到权限集合中
authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
});
//手动给集合中添加权限
// authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
// authorities.add(new SimpleGrantedAuthority("ROLE_CW_MANAGER"));
// authorities.add(new SimpleGrantedAuthority("ROLE_ORDER_MANAGER"));
return authorities;
}
}
-
其中重写UserDetails loadUserByUsername(String username)方法,根据username查询数据库的user对象,使用springframework.security.core.userdetails.User的构造器将查询出来的结果封装到这个对象中并且返回。AuthenticationManager 会自动这样 auth.userDetailsService(userService)进行身份验证,就实现了使用数据库的数据进行验证的功能。
持续更新中。。。