文章目录
Spring-Security,访问控制安全框架
代码链接
- 代码01:
https://gitee.com/daltonwang/ssm-learning/tree/master/spring-security
简单介绍
能够为企业应用提供安全访问控制的安全框架——由Spring提供;
访问控制的几种实现方式:
- Aop、拦截器实现
- 框架实现:Spring-Security,Apache Shiro
简单使用
依赖引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
引入依赖并且运行SpringBoot项目之后,控制台将能够看见默认生成的密码,默认账号为user
修改密码
在配置文件src/main/resources/application.properties
中可修改默认的账号密码
spring.security.user.name=admin
spring.security.user.password=123456
密码加密
密码如果是明文存储则会有巨大的风险,因此需要将明文→密文,然后存储
使用Spring-Security提供的实现类加密
在security
包中建配置类,并通过控制反转新建用于加密的类
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
在测试类中测试两个方法encode
加密方法和matches
明文与密文的比较方法
@SpringBootTest
public class SecurityConfigTest {
@Autowired
PasswordEncoder passwordEncoder;
@Test
public void testSecurity(){
String encode = passwordEncoder.encode("123456");
System.out.println(encode);
}
@Test
public void testSecurityMatch(){
boolean matches = passwordEncoder.matches("123456", "$2a$10$a2fwphTQmI/EwTGkxOkVMOIoaihqfEaVOs6nCr9H6fCcbLkqFziDC");
System.out.println(matches);
}
}
加密的简单写法
在配置文件src/main/resources/application.properties
中的使用密文,并在密码的头部添加{bcrypt}
spring.security.user.name=admin
spring.security.user.password={bcrypt}$2a$10$a2fwphTQmI/EwTGkxOkVMOIoaihqfEaVOs6nCr9H6fCcbLkqFziDC
此时的加密配置类
@Configuration
public class SecurityConfig {}
权限控制
即不同的用户具有不同的访问权限
配置类中配置用户和权限
注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
:告知Spring-Security开启方法权限控制
配置文件SecurityConfig
继承WebSecurityConfigurerAdapter
,在类中重写configure()
方法
在方法中利用inMemoryAuthentication()
创建临时测试用户Tom,为用户分配加密的密码“123456”,并且分配一个测试权限“/user/get”,表示Tom用户拥有访问“/user/get”的权限。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("Tom")
.password("{bcrypt}$2a$10$a2fwphTQmI/EwTGkxOkVMOIoaihqfEaVOs6nCr9H6fCcbLkqFziDC")
.authorities("/user/get","/user/list");
}
}
使用@PreAuthorize("hasAuthority('/user/get')")
约定所拥有的权限
@RestController
@RequestMapping("/portal/user")
public class UserController {
private IUserService userService;
@Autowired
public void setUserService(IUserService userService) {
this.userService = userService;
}
@GetMapping("/get")
@PreAuthorize("hasAuthority('/user/get')")
public User get(Integer id){ return userService.getById(id); }
@GetMapping("/list")
@PreAuthorize("hasAuthority('/user/list')")
public List<User> list(){ return userService.list(); }
}
通过存储的数据进行认证和访问控制
直接将权限和密码放于代码之中是不合理的,因此应该通过数据库等进行存储,需要时再取出来。
Spring-Security认证时候会将用户名传递到
UserDetalisService
的loadUserByUsername
方法获取用户信息。
如下代码就实现了通过用户名得到相应的用户信息。
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
IUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userService.getUserDetails(username);
}
}
引用上述方法,将得到的用户信息传入认证管理构建器中即可进行用户认证
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
从数据库中获取用户数据并且构建
UserDetails
的过程如下:
public interface IUserService extends IService<User> {
UserDetails getUserDetails(String username);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails getUserDetails(String username) {
User user = userMapper.findUserByUserName(username);
if (user==null) { return null; }
List<Permission> userPermissionsById = userMapper.findUserPermissionsById(user.getId());
String[] permissions = new String[userPermissionsById.size()];
int i = 0;
for (Permission permission : userPermissionsById) {
permissions[i++] = permission.getAuthority();
System.out.println(permission.getAuthority());
}
UserDetails userDetails = org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.accountLocked(user.getIsLocked()==1)
.disabled(user.getIsEnabled()==0)
.authorities(permissions)
.build();
return userDetails;
}
}
认证授权配置
即控制网站资源的可访问范围,一些资源无需登录即可访问,一些资源需要登录才能访问。
Spring Security 怎么保证所有向 Spring application 发送请求的用户必须先通过认证?怎么保证用户可以通过表单或者 http 的方式进行认证。解决的办法是Spring Security中有个
WebSecurityConfigurerAdapter
类,程序员通过继承这个类并重写configure(HttpSecurity http)
方法就可以按照场景需求自定义认证和授权。
默认情况下网站的全部资源都被Spring-Security保护起来,所有的资源都必须登录才能查看。默认的源码如下:
// 这是源码中默认的认证和授权的配置。
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //对请求Requests进行授权authorize
.anyRequest().authenticated() //所有请求都需要通过认证
.and().formLogin() //允许用户采用表单登录的方式进行认证
.and().httpBasic(); //允许用户采用 HTTP 基本的认证方式进行认证
}
自定义登录页面认证配置
当配置表单登录后,就需要一个登录页面供用户填写 用户名 和 密码。Spring Secrity 默认了一个页面,如果你觉得它很丑,也可以自己写一个,使用
loginPage("/login")
声明自定义的登录页面所在位置。当用户第一次访问 web 应用时会自动跳转到默认登录界面或者用户自定义的登录界面。
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated() //任何请求都要经过认证
.and()
.formLogin().loginPage("/login").permitAll(); //指定登录页的URL,并允许所有的用户(包括没认证的)访问登录页
}
每个URL都要求用户通过认证,可以通过给
http.authorizeRequests()
方法添加子方法的方式为每个URL指定自定义要求。
http.authorizeRequests()
下添加了多个匹配器,每个匹配器用来控制不同的URL接受不同的用户访问。简单讲,http.authorizeRequests()
就是在进行请求的权限配置。- 所有用户都可以访问以
/resources/**
开头的URL,和/signup
、/about
两个URL。- 拥有
ADMIN
角色的用户可以访问以/admin/
开头的URL。hasRole(String)
:如果当前用户有String表示的角色,则返回True。- 同时拥有
ADMIN
和DBA
角色的用户可以访问以/db/**
开头的URL。access(String)
:当String为true时才可进行访问。- 所有没被匹配器匹配到的URL都需用户通过认证。
and()
返回一个SecurityBuilder
。Spring Security支持两种认证方式:formLogin()
和httpBasic()
。
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() 1
.antMatchers("/resources/**", "/signup", "/about").permitAll() 2
.antMatchers("/admin/**").hasRole("ADMIN") 3
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") 4
.anyRequest().authenticated() 5
.and()
// ...
.formLogin();
}
自定义登录页面具体实现
如果觉得他的登录界面太丑,可以自己设置登录界面
1.引入
Thymeleaf
引擎动态处理页面内容
<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>
2.编写自己的登录界面于
src/main/resources/templates/login.html
- 登录表单属性
action=”/login"method="post"
,也就是点击请求时候向/login
提交post
请求;- 用户名输入框
name="username"
这个是Spring-Security约定的用户名;- 密码输入框
name="password"
,这个也是Spring-Security约定的密码;- 表单中包含一个提交按钮。
<form action="/login" method="post">
<div class="form-group ">
<input type="text" name="username" class="form-control" placeholder="手机号">
</div>
<div class="form-group ">
<input type="password" name="password" class="form-control" placeholder="密码">
</div>
<button type="submit" class="btn btn-primary btn-block btn-flat">
登录
</button>
</form>
3.添加控制器,请求
login.html
时候显示模板文件夹中的登录表单
@RestController
public class SystemController {
@GetMapping("/login.html")
public ModelAndView loginForm(){
return new ModelAndView("login");
}
}
4.配置
SecurityConfig
类的configure(HttpSecurity http)
方法设置表单登录
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests()
.antMatchers(
"/index.html",
"/img/*",
"/js/*",
"/css/*",
"/bower_components/**",
"/login.html").permitAll()
//任何请求需要认证authenticated
.anyRequest().authenticated().and()//采用表单进行认证
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.failureUrl("/login.html?error")
.defaultSuccessUrl( "/index.html").and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login.html?logout" );
}
参考资料
- HttpSecurity初步理解:
https://blog.csdn.net/yy_diego/article/details/92800756