说说SpringSecurity的入门级别的使用吧

本文介绍了如何在SpringBoot项目中使用SpringSecurity进行权限管理,包括添加安全依赖、配置安全设置、自定义用户详情服务、基于角色的访问控制(RBAC)以及动态生成用户权限菜单。此外,还涉及到了登录、登出表单的创建、密码加密以及白名单配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说说SpringSecurity的入门级别的使用吧

在本人大四即将毕业的时候,一个学弟找我做他的课设,这个时候再去做他们的课设,真的觉得太简单了,SpringBoot帮忙做的事太多了,又是简单的CRUD,前端用的模板引擎,为了让他顺利上岸,我觉得帮他加上权限,并尝试了我没有用过的JPA

1、说说安全

安全就得有,在SpringBoot实战一书中,作者已经说明,门要上锁头,信息可能事我们最有价值的东西了。老哥你还用拦截器?还用Session保存用户信息?别了啊!

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
  </dependency>

1.1、配置类

引入依赖之后,你的应用的大门就已经上锁了,不信,你启动项目,绝对会跳出一个登录表单!

package com.cbf.security;


import cn.hutool.core.util.ArrayUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.List;

/**
 * @author wangnaixing
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        List<String> urls = ignoreUrlsConfig.getUrls();
        String[] urlArray = ArrayUtil.toArray(urls, String.class);

        http.authorizeRequests()
                .antMatchers("/").access("permitAll()")
                .antMatchers("/courses").access("hasAnyRole('ROLE_USER')")
                .antMatchers(urlArray).access("permitAll()")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/")
                .loginProcessingUrl("/authenticate")
                .usernameParameter("user")
                .passwordParameter("pwd")
                .defaultSuccessUrl("/permission/getPermissionList",true)
                .and()
                .headers().frameOptions().disable()
                .and()
                .logout()
                .logoutSuccessUrl("/toLogin");


    }

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth)
            throws Exception {

        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(encoder());

    }

}

1.2、让SpringSecurity承认的用户细节

实现了UserDetails接口的类,就等于告诉了SpringSecurity,这是就是我当前用户的信息,就好像你在Session中存入当前用户信息一样。想用的时候,我就可以取出来!

package com.cbf.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

/**
 * @author by wangnaixing
 * @Description 用户详情
 * @Date 2021/12/25 16:04
 */

public class MyUserDetail implements UserDetails {
    /**
     * 用户名
     */
    private final String username;
    /**
     * 密码
     */
    private final String password;
    /**
     * 用户的权限集合
     */
    Collection<? extends GrantedAuthority>  grantedAuthorities;

    public MyUserDetail(String username, String password, List<GrantedAuthority> grantedAuthorities) {
        this.username = username;
        this.password =password;
        this.grantedAuthorities=grantedAuthorities;
    }




    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.grantedAuthorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

1.3、自定义处理UserDetail的业务处理类

和数据库打通,从数据库中根据用户名获取用户信息,可以说用户名在SpringSecurity中很关键,通过这个用户就能得到用户的一切,而做这个事情的人,就是MyUserDetailsService,通过实现UserDetailsService,具备了loadUserByUsername(String username)的能力。

package com.cbf.security;

import com.cbf.pojo.Permission;
import com.cbf.pojo.Role;
import com.cbf.pojo.User;
import com.cbf.repo.UserDao;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * @author by wangnaixing
 * @Description
 * @Date 2021/12/25 14:35
 */
@Slf4j
@Component
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserDao userRepo;

    /**
     * 根据用户名返回用户详情
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("=====系统正在进行身份认证中====");
        User user = userRepo.findByUsername(username);

        if (user != null) {
            //把数据库中的用户包装成为用户详情
            System.out.println("认证成功!当前用户->"+username);
			//拿到用户权限:角色+资源
            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            List<Role> roleList = user.getRoleList();
            List<Permission> permissionList = new ArrayList<>();
            roleList.forEach(x-> permissionList.addAll(x.getPermissionList()));

            roleList.stream()
                    .filter(x->x.getRoleCode()!=null)
                    .forEach(x->grantedAuthorities.add(new SimpleGrantedAuthority(x.getRoleCode())));
            permissionList.stream()
                    .filter(x->x.getPermission()!=null)
                    .forEach(x->grantedAuthorities.add(new SimpleGrantedAuthority(x.getPermission())));

            log.info("--用户权限---");
            grantedAuthorities.forEach(System.out::println);

            return new MyUserDetail(username, user.getPwd(),grantedAuthorities);


        }

        throw new UsernameNotFoundException(
                "User '" + username + "' not found");
    }
}

1.4、基于RCBA,根据用户名获取用户权限

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z8EtjMFY-1640487041766)(D:\my_notebook\typora笔记图片统一管理处\image-20211226103521237.png)]

2、让不同的用户登录有不同的菜单

2.1、后端提供接口

根据用户名,连表,拿到权限表的信息,权限表,就那几个字段,名称啊,url啦,名称就是菜单名了,url就是点击以后怎么跳的问题了,存在级联关联,ID,PID那种,本人用流处理了一下

package com.cbf.web;

import com.cbf.dto.PermissionNode;
import com.cbf.pojo.Permission;
import com.cbf.pojo.User;
import com.cbf.repo.UserDao;
import com.cbf.security.MyUserDetail;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 权限资源控制器
 * @author by wangnaixing
 * @Description
 * @Date 2021/12/25 13:03
 */
@Controller
@RequestMapping("/permission")
public class PermissionController {

    @Autowired
    private UserDao userDao;

    @RequestMapping("/getPermissionList")
    public String getPermissionList(Model model, @AuthenticationPrincipal MyUserDetail principal){
        List<Permission> permissionList = new ArrayList<>();

        //调用服务,根据用户名查询用户信息
        User user = userDao.findByUsername(principal.getUsername());
        //获取该用户权限信息
        user.getRoleList().forEach(x-> permissionList.addAll(x.getPermissionList()));


        //将权限信息转为树形
        List<PermissionNode> permissions = permissionList.stream()
                .filter(x -> x.getPId().equals(0L))
                .map(x -> buildTree(x, permissionList))
                .collect(Collectors.toList());

        model.addAttribute("permissions",permissions);
        return "index";

    }
    /**
     * 构建树形结构
     * @param permission
     * @param permissionList
     * @return
     */
    private PermissionNode buildTree(Permission permission, List<Permission> permissionList) {
        PermissionNode node = new PermissionNode();
        BeanUtils.copyProperties(permission,node);
        List<PermissionNode> children =
                permissionList.stream()
                        .filter(x -> x.getPId().equals(permission.getId()))
                        .map(x -> buildTree(x, permissionList))
                        .collect(Collectors.toList());
        node.setChildren(children);
        return node;
    }


}

package com.cbf.dto;

import com.cbf.pojo.Permission;

import java.util.ArrayList;
import java.util.List;

/**
 * 权限的树形节点
 * @author by wangnaixing
 * @Description
 * @Date 2021/12/25 13:55
 */
public class PermissionNode extends Permission {

    public List<PermissionNode> getChildren() {
        return children;
    }

    public void setChildren(List<PermissionNode> children) {
        this.children = children;
    }

    private List<PermissionNode> children = new ArrayList<PermissionNode>();
}

2.2、侧边栏动态变化

	<div class="leftnav">
		<div class="leftnav-title">
			<strong><span class="icon-list"></span>菜单列表</strong>
		</div>
		<div th:each="item:${permissions}">
			<h2>
				<span class="icon-user" th:text="${item.name}"></span>
			</h2>
			<ul style="display: block" th:if="${item.children!=null}">
				<li th:each="item2:${item.children}"><a th:href="${item2.url}" target="right"><span class="icon-caret-right" th:text="${item2.name}"></span></a></li>
			</ul>
		</div>
	</div>

3、拦截吧,没登陆不给进来,没权限不给访问接口。

3.1、登录表单

处理的路径,处理的用户名,密码统统自定义。

			<form th:action="@{/authenticate}" method="POST">
					<div class="panel loginbox">
						<div class="text-center margin-big padding-big-top">
							<h1>学生体温填报管理信息系统V1.0</h1>
						</div>
						<div class="panel-body"
							style="padding: 30px; padding-bottom: 10px; padding-top: 10px;">
							<div class="form-group">
								<div class="field field-icon-right">
									<input type="text" class="input input-big" name="user"
										placeholder="登录账号" data-validate="required:请填写账号" /> <span
										class="icon icon-user margin-small"></span>
								</div>
							</div>
							<div class="form-group">
								<div class="field field-icon-right">
									<input type="password" class="input input-big" name="pwd"
										placeholder="登录密码" data-validate="required:请填写密码" /> <span
										class="icon icon-key margin-small"></span>
								</div>
							</div>
							<div class="form-group">
								<div class="field">
									<input type="text" class="input input-big" name="code"
										placeholder="填写右侧的验证码" data-validate="required:请填写右侧的验证码" value="6892"/>
									<img src="images/passcode.jpg" alt="" width="100" 
										height="32" class="passcode"
										style="height: 43px; cursor: pointer;"
										onclick="this.src=this.src+'?'">
								</div>
							</div>
						</div>
						<div style="padding: 30px;">
							<input type="submit"
								class="button button-block bg-main text-big input-big"
								value="登录">
						</div>
					</div>
				</form>

3.2、登出表单

<form th:action="@{/logout}" method="POST">
	<button  type="submit"
						class="button button-little bg-red" href="login.html"><span
						class="icon-power-off"></span> 退出登录</button>
</from>

3.3、密文的密码

你在配置中注入加密器!数据存的密码一定是密文。

package com.cbf.security;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @author by wangnaixing
 * @Description
 * @Date 2021/12/25 14:43
 */

@RunWith(SpringRunner.class)
@SpringBootTest
public class PasswordEncoderTest {
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Test
    public void test01(){
        String password = "123456";
        String encode = passwordEncoder.encode(password);
        System.out.println(encode);
    }

}

3.4、白名单

比如一些静态资源不好拦截吧,再拦截样式都没得了,在配置类配置,他接受的参数是一个可变String类型,为了不把数据写死,我使用了写在yaml的方式,去绑定

3.4.1、application.yaml
secure:
  ignored:
    urls:
      - /swagger-resources/**
      - /v2/api-docs/**
      - /swagger-ui.html
      - /*.html
      - /**/*.html
      - /**/*.css
      - /**/*.js
      - /**/*.png
      - /**/*.jpg
3.4.2、配置类

怎么注入详情见配置类

package com.cbf.security;


import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * 用于配置白名单资源路径
 *
 */
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "secure.ignored")
public class IgnoreUrlsConfig {

    private List<String> urls = new ArrayList<>();
}

4、源码在我的资源提供下载!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值