SpringBoot2.1.5
SpringSecurity 是基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,在 Web 请求级和方法调用级处理用户认证(Authentication)和用户授权(Authorization)。他提供了强大的企业安全服务,如:认证授权机制、Web资源访问控制、业务方法调用访问控制、领域对象访问控制Access Control List(ACL)、单点登录(SSO)等等。
核心功能:认证(你是谁)、授权(你能干什么)、攻击防护(防止伪造身份)。
SpringSecurity的核心是一个过滤器链,即一组Filter,所有的请求都会经过这些过滤器,通过拦截url从而实现权限的控制。每个过滤器都有特定的职责,可通过配置添加、删除过滤器。过滤器的排序很重要,因为它们之间有依赖关系。有些过滤器也不能删除,如处在过滤器链最后几环的ExceptionTranslationFilter(处理后者抛出的异常),FilterSecurityInterceptor(最后一环,根据配置决定请求能不能访问服务)。
Spring Security 基于 Spring 框架,一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
Spring Security主要是从两个方面解决安全性问题:
web请求级别:使用servlet过滤器保护web请求并限制URL级别的访问
方法调用级别:使用Spring AOP保护方法调用,确保具有适当权限的用户采用访问安全保护的方法.
org.springframework.boot
spring-boot-starter-security
@RestController
public class UserController
{
@RequestMapping(value = "/test")
public String test() throws Exception
{
return "test success";
}
}
在不做任何配置的情况下,security会把服务内所有资源的访问都保护起来,需要先进行身份证认证才可访问, 使用默认的表单登录或http basic认证方式。
启动SpringBoot,访问http://localhost:8080/test,由于我们开启了SpringSecurity且当前是未登录状态,页面会被302重定向到http://localhost:8080/login登录页面。
用户名:user,密码在控制台输出中:d2ee3df0-1924-440a-9b8e-7f3449ee798f
输入正确的用户名密码点击登录,系统重新302到http://localhost:8080/test并显示返回的数据。即:这表示我们的接口已经被spring保护了。
在application.properties中做如下设定,使用用户名admin密码123访问系统。
spring.security.user.name=admin
spring.security.user.password=123
新建Java类WebSecurityConfigurer
使用@Configuration和@EnableWebSecurity对Java类进行注解声明配置,继承WebSecurityConfigurerAdapter。
使用@Configuration和@EnableWebSecurity对Java类进行配置,继承WebSecurityConfigurerAdapter,对里面的方法重写就可以了(这样Springboot将不会初始化默认的WebSecurityConfigurerAdapter ),分别是对AuthenticationManagerBuilder,WebSecurity,HttpSecurity方法,我们主要介绍AuthenticationManagerBuilder和HttpSecurity,通过对这两种方法重写最终实现我们自定义认证;
想要关闭UserDetailsService的自动配置,则添加一个类实现UserDetailsService,改变认证的用户信息来源,我们可以实现 UserDetailsService。AuthenticationProvider或AuthenticationManager 来覆盖Springboot默认实现。
认证是由 AuthenticationManager 来管理的,但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。AuthenticationManager 中可以定义有多个 AuthenticationProvider。当我们使用 authentication-provider 元素来定义一个 AuthenticationProvider 时,如果没有指定对应关联的 AuthenticationProvider 对象,Spring Security 默认会使用 DaoAuthenticationProvider。DaoAuthenticationProvider 在进行认证的时候需要一个 UserDetailsService 来获取用户的信息 UserDetails,其中包括用户名、密码和所拥有的权限等。所以如果我们需要改变认证的方式,我们可以实现自己的 AuthenticationProvider;如果需要改变认证的用户信息来源,我们可以实现 UserDetailsService。
通过管理器增加了两个用户:admin,admin;guest,guest。
InMemoryUserDetailsManager:顾名思义,将用户名密码存储在内存中的用户管理器。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(new MyPasswordEncoder())
.withUser("admin").password("admin").roles("");
auth.inMemoryAuthentication()
.passwordEncoder(new MyPasswordEncoder())
.withUser("guest").password("guest").roles("");
}
}
MyPasswordEncoder.java
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
}
实现用户、角色、权限的动态管理
数据库表为5个分别是: 用户表、角色表、权限表、用户角色中间表、角色权限中间表
用户表
drop table if exists `user`;
create table if not exists `user`(
id int(10) not null primary key AUTO_INCREMENT,
username varchar(100),
password varchar(100),
token varchar(100)
) Engine=InnoDB DEFAULT charset=utf8;
insert into user(username,password) values("admin","$2a$10$XTyME7htOf3pYO0MsUcWCOW4nkpnJ1NDk9TLF4FbpmQxc0qrrsqsm");
insert into user values(2,"user","$2a$10$XTyME7htOf3pYO0MsUcWCOW4nkpnJ1NDk9TLF4FbpmQxc0qrrsqsm", null);
角色表Role
drop table if exists `role`;
create table if not exists `role`(
id int(10) not null primary key AUTO_INCREMENT COMMENT '角色编号' ,
name varchar(100) COMMENT '角色名称'
) Engine=InnoDB DEFAULT charset=utf8 COMMENT = '角色表';
--insert into role(name) values("ROOT");
insert into role(name) values("ROLE_ADMIN");
insert into role(name) values("ROLE_USER");
drop table if exists `role_user`;
create table if not exists `role_user`(
rid int(10) not null,
uid int(10) not null,
unique `uk_rid_uid`(rid, uid)
) Engine=InnoDB DEFAULT charset=utf8 COMMENT = '用户角色中间表';
insert into role_user values(1, 1);
insert into role_user values(2, 2);
Permission表
drop table if exists `Permission`;
create table if not exists `Permission`(
id int(10) not null primary key,
text varchar(100),
href varchar(100),
pid int(100),
descx varchar(100)
);
insert into Permission values(100, "控制台", "/", 100, "index");
insert into Permission values(200, "基础管理", "/manage", 200, "jichu");
insert into Permission values(210, "部门管理", "/dept.html", 200, "DeptManage");
insert into Permission values(220, "用户管理", "/user", 200, "UserManage");
insert into Permission values(230, "角色管理", "/role.html", 200, "DeptManage");
insert into Permission values(240, "角色用户表", "/roleUser", 200, "DeptManage");
insert into Permission values(250, "角色权限表", "/rolePermission", 200, "DeptManage");
insert into Permission values(260, "菜单管理", "/Menu", 200, "菜单管理descx");
User.java实体类
public class User implements UserDetails {
public int id;
public String username;
public String password;
public List roles;
public List permissions;
@Override
public Collection extends GrantedAuthority> getAuthorities() {
List authorities = new ArrayList<>();
for(Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
Role.java
public class Role
{
public int id;
public String name;
//getter、setter
...
Permission.java
与BootStrap-treeview插件所返回的JSON参数一致,则前端可直接引入data:result。
public class Permission implements Serializable
{
private static final long serialVersionUID = 1L;
//权限id
public int id;
//权限名称
public String text;
//授权链接
public String href;
//权限类型(menu、button)
//public String type;
//权限描述
public String descx;
//父节点id
public int pid;
//子节点
public List nodes = new ArrayList<>();
RoleUser.java
public class RoleUser
{
public int uid;
public int rid;
//getter、setter
...
RolePermission.java
public class RolePermission
{
public int rid;
public int pid;
//getter、setter
...
关闭CSRF跨域 CSRF(Cross-site request forgery)跨站请求伪造,也被称为one-click attack单键攻击;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private LoginAuthenticationSuccess authenticationSuccess;//验证成功的处理类
@Bean
protected UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
System.out.println("configure(AuthenticationManagerBuilder auth)...");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() //其他地址的访问均需验证权限
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/loginSub")
//.defaultSuccessUrl("/")
.successHandler(authenticationSuccess)
.failureUrl("/failure.html")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.permitAll();
http.csrf().disable();
//
//http.sessionManagement().invalidSessionUrl("/session/invalid"); //session失效时间
//http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); //无权访问处理器
}
@Override
public void configure(WebSecurity web) throws Exception {
System.out.println("configure(WebSecurity web)...");
//解决静态资源被拦截的问题
web.ignoring().antMatchers("/static/**");
web.ignoring().antMatchers("/bootstrap/**");
web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/js/**");
web.ignoring().antMatchers("/images/**");
web.ignoring().antMatchers("/**/favicon.ico");
web.ignoring().antMatchers("/lib/**");
web.ignoring().antMatchers("/fonts/**");
web.ignoring().antMatchers("/lang/**");
web.ignoring().antMatchers("/login.html");
//解决服务注册url被拦截的问题
web.ignoring().antMatchers("/**/*.json");
}
}
LoginAuthenticationSuccess.java
@Component
public class LoginAuthenticationSuccess extends SimpleUrlAuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException
{
System.out.println("---Authentication Success(登录成功): " + authentication);
System.out.println("---getPrincipal: " + authentication.getPrincipal());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
UserDetailsServiceImpl.java
父子菜单权限区分
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private LoginDao loginDao;
@Autowired
private PermissionDao permissionDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = loginDao.loadUserByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("用户名不存在!" + username);
}
List permissions = permissionDao.findPermissionsByUserId(user.id);
List pers = new ArrayList<>();
for(Permission p : permissions) {
if(p.id == p.pid) { //200/200
pers.add(p);
}
}
pers.forEach(e ->
{
for (Permission p : permissions) {
if (p.id != p.pid && p.pid == e.pid) //210/200
{
e.nodes.add(p);
}
}
});
System.out.println("Permissions列表数据:" + pers);
user.permissions = pers;
return user;
}
}
192.168.236.128:6379> keys *
1) "spring:session:sessions:a1287a8d-bb00-4a3f-9335-da69b14d4510"
2) "dept_list::count"
3) "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:admin"
4) "spring:session:sessions:expires:a1287a8d-bb00-4a3f-9335-da69b14d4510"
5) "spring:session:expirations:1571032080000"
6) "dept_list::0"
config()中添加
.sessionManagement()
.invalidSessionUrl("/login/invalid")
.maximumSessions(1)
// 当达到最大值时,是否保留已经登录的用户
.maxSessionsPreventsLogin(false)
// 当达到最大值时,旧用户被踢出后的操作
.expiredSessionStrategy(new CustomExpiredSessionStrategy())
如果只配置loginPage而不配置loginProcessingUrl的话
那么loginProcessingUrl默认就是loginPage
你配置的loginPage("/testpage.html") ,那么loginProcessingUrl就是"/testpage.html"
优先级从上向下:
.successHandler(authenticationSuccess)
.successHandler(new ForwardAuthenticationSuccessHandler("/index2"))
.defaultSuccessUrl("/index1")
多个请求共享认证信息
https://blog.csdn.net/yuanlaijike/article/details/84703690
Spring Boot2.0使用Spring Security
https://www.cnblogs.com/wtzbk/p/9387859.html
关于Boot应用中集成Spring Security你必须了解的那些事
https://www.bbsmax.com/A/A2dmY2DWde/
Spring Boot2.0 集成Security QQ登录实现APP ID、APP Key
https://blog.csdn.net/qq_35508033/article/details/79046441
Spring Security OAuth2 SSO
https://www.cnblogs.com/cjsblog/p/9296361.html
基于SpringBoot搭建应用开发框架(二) —— 登录认证
https://www.cnblogs.com/chiangchou/p/springboot-2.html#_label2_2
SpringBoot2.x整合Security5(完美解决 There is no PasswordEncoder mapped for the id “null”)
https://blog.csdn.net/SWPU_Lipan/article/details/80586054
https://blog.csdn.net/qq_21963133/article/details/81066714