springboot集成springsecurity

举报
object 发表于 2022/06/25 11:17:03 2022/06/25
1.3k+ 0 1
【摘要】 springsecurity作为一款成熟的安全认证框架,对于简单的系统架构,有着集成即用,简单有效的优点,因此做一个简单的springboot集成springsecurity的代码演示,总结归纳。源代码文件所有文件展示如下依赖组件pom.xml<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.o...

springsecurity作为一款成熟的安全认证框架,对于简单的系统架构,有着集成即用,简单有效的优点,因此做一个简单的springboot集成springsecurity的代码演示,总结归纳。

源代码文件

所有文件展示如下

依赖组件pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>security</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--mybatis-plus 是自己开发的,非官方的!-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1.tmp</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <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>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

applicaiton.properties配置

spring.security.user.name=root
spring.security.user.password=123456

spring.datasource.driver-class-name: com.mysql.cj.jdbc.Driver
spring.datasource.url: jdbc:mysql://localhost:3306/sans_security?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
spring.datasource.username: root
spring.datasource.password: 123456

SecurityConfig配置文件

/**
 * security配置
 * 配置登录登出,权限认证,页面跳转,认证失败或者成功处理,部分认证开启或者关闭
 *
 * @author object
 * @since 2022年6月25日
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private PersistentTokenRepository tokenRepository;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    // 设置加密函数
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 设置token仓库,持久化数据。用于rememberMe功能,识别用户登录状态
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }

    // 自定义配置,登录登出接口,权限开关开启关闭等
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        // 配置没有权限访问跳转自定义页面
        httpSecurity.exceptionHandling().accessDeniedPage("/403.html");

        httpSecurity.formLogin() // 自定义编写的登录页面
                        .loginPage("/login.html") // 登录页面设置
                        .loginProcessingUrl("/user/login") // 登录访问路径
                        .defaultSuccessUrl("/success.html").permitAll() // 登录后默认跳转路径
                        .and()
                    .logout() // 自定义编写登出页面
                        .logoutUrl("/logout") // 登出url
                        .logoutSuccessUrl("/login.html").permitAll() // 跳转地址
                        .and().authorizeRequests() // 请求认证
                            .antMatchers("/hello").permitAll()
//                            .antMatchers("/", "/user/login").permitAll() // 不需要认证的路径
//                            .antMatchers("/test/addUser").hasAuthority("addUser")
                            .antMatchers("/hello2").hasAnyAuthority("addUser,finadAll")
                            .antMatchers("/hello3").hasRole("admin")
                            .antMatchers("/hello4").hasRole("admin4")
                            .antMatchers("/test/hello").hasAnyRole("admin")
                        .anyRequest().authenticated() // 任何请求都需要认证
                        .and()
                            .rememberMe() // rememberMe,记住我功能
                            .tokenRepository(tokenRepository) // token仓库
                            .tokenValiditySeconds(600) // token有效时间
                            .userDetailsService(userDetailsService)
                        .and().csrf().disable(); // 关闭csrf校验
    }
}

MyUserDetailsService.java

/**
 * 自定义userDetailsService, 用于登录时校验用户和密码,设置用户权限等
 *
 * @author object
 * @since 2022年6月25日
 */
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UsersMapper usersMapper;

    // 登录时,校验用户密码,查询用户权限
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        QueryWrapper<UserEntity> userEntityQueryWrapper = new QueryWrapper<>();
        userEntityQueryWrapper.eq("username", userName);
        UserEntity userEntity = usersMapper.selectOne(userEntityQueryWrapper);

        if (userEntity == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("finadAll,ROLE_admin,ROLE_admin5,hello6");
        return new User(userEntity.getUsername(), new BCryptPasswordEncoder().encode(userEntity.getPassword()), auths);
    }
}

用户相关的代码

UserEntity

/**
 * 用户实体
 *
 * @author object
 * @since 2022年6月25日
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("users")
public class UserEntity {
    private Integer id;
    private String username;
    private String password;
}

UserMapper

/**
 * 用户mapper
 *
 * @author object
 * @since 2022年6月25日
 */
@Mapper
public interface UsersMapper extends BaseMapper<UserEntity> {
}

User

/**
 * springsecurity提供的用户model,用于认证过程中储存用户信息和认证
 * springsecurity也提供了默认的org.springframework.security.core.userdetails.User实体,
 *  项目一般使用自己的,可以定义一些自定义的字段
 *
 * @author object
 * @since 2022年6月25日
 */
public class User implements UserDetails, CredentialsContainer {
    private static final long serialVersionUID = 530L; // 反序列化
    private static final Log LOG = LogFactory.getLog(User.class);

    private String password;
    private final String username;
    private final Set<GrantedAuthority> authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;
    public User(String username, String password,
                Collection<? extends GrantedAuthority> authorities) {
        this(username, password, true, true, true, true, authorities);
    }

    public User(String username, String password, boolean enabled,
                boolean accountNonExpired, boolean credentialsNonExpired,
                boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {

        if (((username == null) || "".equals(username)) || (password == null)) {
            throw new IllegalArgumentException(
                    "Cannot pass null or empty values to constructor");
        }

        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.credentialsNonExpired = credentialsNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
    }

    private static SortedSet<GrantedAuthority> sortAuthorities(
            Collection<? extends GrantedAuthority> authorities) {
        Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");

        SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(
                new AuthorityComparator());

        for (GrantedAuthority grantedAuthority : authorities) {
            Assert.notNull(grantedAuthority,
                    "GrantedAuthority list cannot contain any null elements");
            sortedAuthorities.add(grantedAuthority);
        }

        return sortedAuthorities;
    }

    private static class AuthorityComparator implements Comparator<GrantedAuthority>,
            Serializable {
        private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

        public int compare(GrantedAuthority g1, GrantedAuthority g2) {
            // Neither should ever be null as each entry is checked before adding it to
            // the set.
            // If the authority is null, it is a custom authority and should precede
            // others.
            if (g2.getAuthority() == null) {
                return -1;
            }

            if (g1.getAuthority() == null) {
                return 1;
            }

            return g1.getAuthority().compareTo(g2.getAuthority());
        }
    }

    @Override
    public void eraseCredentials() {

    }

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

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

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

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

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

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

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

LoginApi

/**
 * 测试接口
 *
 * @author object
 * @since 2022年6月25日
 */
@RestController
public class LoginApi {

    // 测试系统是否正常能访问
    @RequestMapping(value = "/hello",method = RequestMethod.GET)
    public String hello(){
        return "hello security";
    }

    // 测试权限 .antMatchers("/hello2").hasAnyAuthority("addUser,finadAll")
    @RequestMapping(value = "/hello2",method = RequestMethod.GET)
    public String hello2(){
        return "hello2 security";
    }

    // 测试权限 .antMatchers("/hello3").hasRole("admin")
    @RequestMapping(value = "/hello3",method = RequestMethod.GET)
    public String hello3(){
        return "hello3 security";
    }

    // 测试权限 .antMatchers("/hello4").hasRole("admin4")
    @RequestMapping(value = "/hello4",method = RequestMethod.GET)
    public String hello4(){
        return "hello4 security";
    }

    // 测试权限 .antMatchers("/test/hello").hasAnyRole("admin")
    @RequestMapping(value = "/hello5",method = RequestMethod.GET)
    @Secured({"ROLE_admin5"})
    public String hello5(){
        return "hello5 security";
    }

    // 测试权限 @PreAuthorize("hasAuthority('hello6')")
    @RequestMapping(value = "/hello6",method = RequestMethod.GET)
    @PreFilter("filterObject.id%2==0") // 调用前过滤数据
    @PreAuthorize("hasAuthority('hello6')")
    public String hello6(){
        return "hello6 security";
    }

    // 测试权限 @PostAuthorize("hasAuthority('hello7')")
    @RequestMapping(value = "/hello7",method = RequestMethod.GET)
    @PostAuthorize("hasAuthority('hello7')") // 调用后校验
    @PostFilter("filterObject.username == 'admin'") // 校验权限后,过滤数据
    public String hello7(){
        return "hello7 security";
    }
}

前端静态页面

403.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>403</title>
</head>
<body>

<h1>对不起,您没有访问权限!</h1>

</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="/user/login" method="post">
    <!--注意:页面提交方式必须为 post 请求,用户名,密码必须为username,password
    可以通过 usernameParameter()和 passwordParameter()方法修改默认配置-->
    用户名:<input type="text" name="username">
    <br/>
    密码:<input type="text" name="password">
    <br/>
    <input type="submit" value="login">
    <!--name 属性值必须位 remember-me.不能改为其他值-->
    记住我:<input type="checkbox" name="remember-me" title="记住密码"/><br/>

</form>
</body>
</html>

success.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>success</title>
</head>
<body>
登录成功<br> <a href="/logout">退出</a>
</body>
</html>

主入口

SecurityApplication.java

@SpringBootApplication
@MapperScan("com.example.security.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) // 开启注解权限方式,允许先查后校验以及查询过滤
public class SecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SecurityApplication.class, args);
    }
}

部分关键说明

  SecurityConfig.java

    springsecurity关键配置文件,涉及各种详细配置,登录登出,接口配置,白名单配置,认证失败策略配置,csrf开启关闭等,是非常重要的配置文件,文中只写了一些简单的配置,像失败策略,成功策略等都没有配置。

  User.java

     springsecurity认证过程中,用于存储用户信息的载体。以及认证通过后,再次请求时,获取请求的用户信息。

     例如获取用户信息的方法:

SecurityContextHolder.getContext().getAuthentication()
request.getUserPrincipal()

  MyUserDetailsService.java

     重写登录时,校验用户是否存在和密码准确的试验代码,同时封装User用户信息并返回。

  接口权限配置

     一般接口的权限配置,不会像代码演示中一样写死,一个接口一个接口配置,需要的角色权限。一般都会配置在数据库中,动态的变更。除非系统接口较少,用uri加以区分。

  记住我功能

      记住我功能的需要在securityConfig中开启,并配置token工厂。如果需要持久化到数据库,则还需要创建表。sql语句在JdbcTokenReposityoryImpl中直接copy即可。

      org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl

/** Default SQL for creating the database table to store the tokens */
public static final String CREATE_TABLE_SQL = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, "
      + "token varchar(64) not null, last_used timestamp not null)";

     新建表后,数据会自动填充到改表。

     最后,需要使用rememberMe功能。需要在login的时候,传入remember-me参数,login.html中已设置改值。这样才能完美使用记住我,这样即使浏览器关闭,只要cookie中的remererber-me属性还在,就可以继续访问系统无需认证。

记住我:<input type="checkbox" name="remember-me" title="记住密码"/><br/>

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。