SpringSecurity之入门程序-动态控制

官方文档

https://spring.io/projects/spring-security/

安全框架

核心:用户认证,用户授权
1、解决分布式权限安全问题
2、与spring无缝
3、重量级,多依赖

入门案例

1、数据库准备
数据库设计-RBAC
2、SpringBoot项目基础搭建
导入依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
    </parent>


    <dependencies>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- springboot整合freemarker -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

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

        <!-- springboot 整合mybatis框架 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <!-- alibaba的druid数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.9</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

    </dependencies>

创建实体类

// 角色信息表
@Data
public class RoleEntity {
	private Integer id;
	private String roleName;
	private String roleDesc;
}
// 用户信息表
@Data
public class UserEntity implements UserDetails {

	private Integer id;
	private String username;
	private String realname;
	private String password;
	private Date createDate;
	private Date lastLoginTime;
	private boolean enabled;
	private boolean accountNonExpired;
	private boolean accountNonLocked;
	private boolean credentialsNonExpired;
	// 用户所有权限
	private List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

	public Collection<? extends GrantedAuthority> getAuthorities() {

		return authorities;
	}

}

用户实体类继承UserDetails ,使用权限列表的容器

// 权限表
@Data
public class PermissionEntity {
	private Integer id;
	// 权限名称
	private String permName;
	// 权限标识
	private String permTag;
	// 请求url
	private String url;
}

配置文件-YML格式

# 配置freemarker
spring:
  freemarker:
    # 设置模板后缀名
    suffix: .ftl
    # 设置文档类型
    content-type: text/html
    # 设置页面编码格式
    charset: UTF-8
    # 设置页面缓存
    cache: false
    # 设置ftl文件路径
    template-loader-path:
      - classpath:/templates
  # 设置静态文件路径,js,css等
  mvc:
    static-path-pattern: /static/**
  datasource:
    name: test
    url: jdbc:mysql://127.0.0.1:3306/mayikt_rbac
    username: root
    password: 123123
    # druid 连接池
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver

前端页面准备

登陆界面

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>Insert title here</title>
</head>
<body>

<h1>SpringSecurity权限控制登陆系统</h1>
<form action="/login" method="post">
    <span>用户名称</span><input type="text" name="username"/> <br>
    <span>用户密码</span><input type="password" name="password"/> <br>
    <input type="submit" value="登陆">

</form>

<#if RequestParameters['error']??>
    用户名称或者密码错误
</#if>


</body>
</html>

首页界面

<h1>权限控制页面首页</h1>

创建启动类

@MapperScan("com.mayikt.mapper")
@SpringBootApplication
public class AppSecurity {
    public static void main(String[] args) {
        SpringApplication.run(AppSecurity.class);
    }
}

创建服务类实现 UserDetailsService接口

完成账户的权限查询及放入

@Component
@Slf4j
public class MemberUserDetailsService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    /**
     * loadUserByUserName
     *                      登录成功进入
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.根据该用户名称查询在数据库中是否存在
        UserEntity userEntity = userMapper.findByUsername(username);
        if (userEntity == null) {
            return null;
        }
        // 2.查询对应的用户权限
        List<PermissionEntity> listPermission = userMapper.findPermissionByUsername(username);
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        // Lambda表达式
        listPermission.forEach(user -> {
            //将权限添加到列表
            authorities.add(new SimpleGrantedAuthority(user.getPermTag()));
        });
        log.info(">>>authorities:{}<<<", authorities);
        // 3.将该权限添加到security
        userEntity.setAuthorities(authorities);
        return userEntity;
    }

//    public static void main(String[] args) {
//        UserDetailsService userDetailsService = (username) -> {
//            return null;
//        };
//    }
}

账户查询设计数据库:
Mapper层
用户信息查询

public interface UserMapper {
    /**
     * 根据用户名称查询
     *
     * @param userName
     * @return
     */
    @Select(" select * from sys_user where username = #{userName}")
    UserEntity findByUsername(@Param("userName") String userName);

    /**
     * 查询用户的权限根据用户查询权限
     *
     * @param userName
     * @return
     */
    @Select(" select permission.* from sys_user user" + " inner join sys_user_role user_role"
            + " on user.id = user_role.user_id inner join "
            + "sys_role_permission role_permission on user_role.role_id = role_permission.role_id "
            + " inner join sys_permission permission on role_permission.perm_id = permission.id where user.username = #{userName};")
    List<PermissionEntity> findPermissionByUsername(@Param("userName") String userName);
}

权限信息

public interface PermissionMapper {

    @Select(" select * from sys_permission ")
    List<PermissionEntity> findAllPermission();

}

配置类中回调并验证,设置拦截

@Component
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MemberUserDetailsService memberUserDetailsService;
    @Autowired
    private PermissionMapper permissionMapper;

    /**
     * 添加授权账户
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        // 设置用户账号信息和权限
//        auth.inMemoryAuthentication().withUser("mayikt_admin").password("mayikt")
//                .authorities("addMember", "delMember", "updateMember", "showMember");
//        // 如果mayikt_admin账户权限的情况 所有的接口都可以访问,如果mayikt_add 只能访问addMember
//        auth.inMemoryAuthentication().withUser("mayikt_add").password("mayikt")
//                .authorities("addMember");
        //回调加权限。转化加密,验证
        auth.userDetailsService(memberUserDetailsService).passwordEncoder(new PasswordEncoder() {
            /**
             * 对密码MD5加密处理
             * @param rawPassword
             * @return
             */
            @Override
            public String encode(CharSequence rawPassword) {
                return MD5Util.encode((String) rawPassword);
            }

            /**
             * rawPassword 用户输入的密码
             * encodedPassword 数据库DB的密码
             *
             * @param rawPassword
             * @param encodedPassword
             * @return
             */
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                //转化用户登录密码MD5
                String rawPass = MD5Util.encode((String) rawPassword);
                //比较
                boolean result = rawPass.equals(encodedPassword);
                return result;
            }
        });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        //配置httpBasic Http协议认证
////        http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();
//        //配置httpBasic Http协议认证
//        http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and()
//                .formLogin();
//        // 默认fromLog
        List<PermissionEntity> allPermission = permissionMapper.findAllPermission();
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
                expressionInterceptUrlRegistry = http.authorizeRequests();
        allPermission.forEach((permission) -> {
            expressionInterceptUrlRegistry.antMatchers(permission.getUrl()).
                    hasAnyAuthority(permission.getPermTag());
        });
        expressionInterceptUrlRegistry.antMatchers("/login").permitAll()//放行

                .antMatchers("/**").fullyAuthenticated()//拦截所有
                .and().formLogin().loginPage("/login").and().csrf().disable();//跳转向登陆界面
        //.antMatchers("/addMember") 配置addMember请求权限 hasAnyAuthority("addMember")

    }

    /**
     * There is no PasswordEncoder mapped for the id "null"
     * 原因:升级为Security5.0以上密码支持多中加密方式,恢复以前模式
     *
     * @return
     */
    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

}

权限实现思路

账户登录时配置类SecurityConfig接收请求

configure(AuthenticationManagerBuilder auth)中:
MemberUserDetailsService对象回调,进行账户名查询验证并根据账户名进行权限查询及放入

configure(HttpSecurity http) 中
进行权限验证

转回配置类,进行密码加密处理及验证
返回true,登录成功

一个请求过来时通过各个过滤器,最后通过FilterSecurityInterceptor来判断这个请求url是否是不需要验证的,
如果是就直接访问到我们的接口api,如果不是的话,再判断当前请求线程中是否有authentication的认证对象,
如果有就放行,如果没有就返回登录页面(比如这里我们设置的是登录表单的方式),
来到登录页面输入账号密码登录后就会来到 UsernamePasswordAuthenticationFilter,
经过一系列的操作,最后验证成功就会把认证对象authentication放进securityContext中,
然后FilterSecurityInterceptor判断到当前请求线程中这个认证对象就放行,返回的时候最后会通过securityContextpersistenceFilter,
判断当前线程是否有securityContext,如果有就放进session,
那么下次再请求这个url的时候会首先通过securityContextpersistenceFilter这个过滤器,
判断session中是否有securityContextduxiiang,如果有就放进当前请求线程中,然后最后经过FilterSecurityInterceptor时再判断当前请求线程是否有认证对象,
由于最前面经过securityContextpersistenceFilter,已经从session中把认证对象放进了当前请求线程中,所以FilterSecurityInterceptor会直接放行,这样就访问到我们的接口api。

posted @   东楚  阅读(140)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示