[Spring Boot 5]安全管理

news2025/1/24 8:48:30

本文介绍了Spring Security 以及Shiro 在Spring Boot中的使用,对于Spring Security 有基于传统认证方式的Session认证,也有使用OAuth 协议的认证。一般来说,传统的Web架构中,使用Session 认证更加快速,但是,若结合微服务,前后端分离等架构时,则使用OAuth 认证更加方便。

在Spring Boot项目中,Spring Security 整合相对更加容易,可以首选,Shiro 虽不及其强大,但也能胜任绝大部分的项目了。

本文没有细谈密码学,关于这部分的加密内容,可以参见我之前密码学的文章,安全管理中对于密码还是相当看重的。不过在安全管理中,除了密码学认证同时还有权限角色的认证等,值得学习。

目录

  • 引言
  • Spring Security 基本配置
    • 基本用法
    • 基于内存的认证
    • HttpSecurity
    • 登陆表单详细配置
    • 注销登录配置
    • 方法安全
  • 基于数据库的认证
  • OAuth 2
    • OAuth 2授权模式
    • 实践
  • Spring Boot 整合 Shiro

引言

在Java 开发领域常见的安全框架有Shiro 和Spring Security。Shiro 是一个轻量级的安全管理框架,提供了认证、授权、会话管理、密码管理、缓存管理等功能。Spring Security是一个相对复杂的安全管理框架,对OAuth 2的支持也更友好,可以和Spring 框架无缝整合。

Spring Security 基本配置

基本用法

添加依赖:

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

项目中的资源就会被保护起来。
下面添加一个接口:

 @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

启动后,访问80/hello接口,会自动跳转到这个登陆页面,这个页面就是由Spring Security提供的。
在这里插入图片描述
用户名就是user,密码如下图得到:默认的密码随项目随机生成的,查看项目启动日志就行。
在这里插入图片描述
当然了,可以自定义配置用户名和密码:
在配置文件:
具有的角色是admin

spring.security.user.name=jacin
spring.security.user.password=123
spring.security.user.roles=admin

基于内存的认证

开发者可以自定义类继承自WebSecurityConfigurerAdapter ,进而实现对Spring Secuity进行更多的配置:

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin").password("123").roles("ADMIN","USER")
                .and()
                .withUser("jacin").password("123").roles("ADMIN");
    }
}

继承了 WebSecurityConfigurerAdapter 并重写了configure方法,在这里配置了两个用户,一个是admin 具有两个角色,另一个是jacin 角色是admin。
在Spring Security 5.x 引入了众多密码加密方式,本案例使用的是NoOpPasswordEncoder 不加密。

HttpSecurity

可以实现认证功能,但是受保护的资源是默认的,不能根据实际情况进行角色管理。如果要实现功能,就要重写WebSecurityConfigurerAdapter的方法:

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("root").password("123").roles("ADMIN","DBA")
                .and()
                .withUser("admin").password("123").roles("ADMIN","USER")
                .and()
                .withUser("user").password("123").roles("USERS");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception{
    //调用了authorizeRequests()方法开启HttpSecurity配置
        http.authorizeRequests()
        //其中/admin/**的URL必须具有admin的角色
                .antMatchers("/admin/**")
                .hasRole("ADMIN")
                .antMatchers("/users/**")
                .access("hasAnyRole('ADMIN','USER')")
                .antMatchers("/db/**")
                .access("hasAnyRole('ADMIN') and hasRole('DBA')")

			//下两行表示除了前面定义的URL模式以外,用户访问其他的URL都必须认证后访问
                .anyRequest()
                .authenticated()
                .and()
                // 开启表单登陆
                .formLogin()
                //配置此接口方便Ajax 或者 移动端调用登录接口
                .loginProcessingUrl("/login")
				// 和登录相关的接口不需要认证即可访问
                .permitAll()
                .and()
                // 关闭csrf
                .csrf()
                .disable();
    }
}

这里设置了三个用户,分别具有不同的角色。
下面在controller进行测试:

// 此Controller层为RestController
    @GetMapping("/admin/hello")
    public String admin() {
        return "hello admin!";
    }

    @GetMapping("/user/hello")
    public String user() {
        return "hello user";
    }

    @GetMapping("/db/hello")
    public String dba() {
        return "hello dba";
    }

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

测试的时候,会发现登陆相应的页面。例如管理登陆后可以访问/admin/**,/user/ 界面。

登陆表单详细配置

到目前为止,登陆表单使用的是Spring Security 提供的页面,登陆成功后也是默认的页面跳转。但是前后端分离是开发的主流,在此开发模式下,前后端数据通过JSON进行,这是登陆后就不是页面跳转了,而是一段JSON提示。
和上文一样,这里给出部分代码,其余部分见上文

				.formLogin()
				//登陆页面。这里的/login就是开发者自定义的登陆页面,也就是路径
				.loginPage("/login")
				// 配置了loginProcessingUrl,登陆请求处理接口
                .loginProcessingUrl("/login")
                // 认证需要的参数
                .usernameParameter("name")
                .passwordParameter("passwd")
                // 登陆成功的处理逻辑,本次是返回一段JSON,第三个参数是当前用户登陆信息
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        Object principal = authentication.getPrincipal();
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        response.setStatus(200);
                        Map<String,Object> map = new HashMap<>();
                        map.put("status",200);
                        map.put("msq",principal);
                        ObjectMapper om = new ObjectMapper();
                        out.write(om.writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        response.setStatus(401);
                        Map<String,Object> map = new HashMap<>();
                        map.put("status",401);
                        if(exception instanceof LockedException) {
                            map.put("msg","账户被锁定");
                        } else if (exception instanceof BadCredentialsException) {
                            map.put("msg","账户或者密码输入错误");
                        } else if (exception instanceof DisabledException) {
                            map.put("msg","账户被禁用");
                        } else if (exception instanceof AccountExpiredException) {
                            map.put("msg","账户过期");
                        }
                        ObjectMapper om = new ObjectMapper();
                        out.write(om.writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()

在templates下面建一个简单的提交表单:

<form  method="post">
  name <input name="name" type="text">
    pass <input name="passwd" type="password">
    <input type="submit">
</form>

然后在Controller写跳转页面:
这里返回的就是视图层而不是字符串。

@Controller
public class testController {
    @GetMapping("/login")
    public String login() {
        return "login_page";
    }
}

当然我们也可以用postman来进行测试:
在这里插入图片描述
在这里插入图片描述
这里用到了Ajax技术。

注销登录配置

如果想注销登录也只需要提供简单的配置:

.and()
// 开启注销登陆的配置
                .logout()
                .logoutUrl("/logout")
                // 清除身份认证信息
                .clearAuthentication(true)
                // 使session失效
                .invalidateHttpSession(true)
                //可以写一下数据清除工作
                .addLogoutHandler(new LogoutHandler() {
                    @Override
                    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

                    }
                })
                // 注销成功后的业务逻辑
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.sendRedirect("/login");
                    }
                })
                .and()

方法安全

上述的认证和授权都是基于URL的,开发者也可以通过注解来灵活配置方法安全,要通过注解来开启基于注解的安全配置:

@Configuration
// 解锁两个注解
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class SecutityCon {
}

开启安全配置后,可以创建一个Service来测试:

@Service
public class MethodService {
// 访问该方法需要admin角色,注意在角色面前需要加ROLE_
    @Secured("ROLE_ADMIN")
    public String admin() {
        return "hello admin";

    }
// 既需要ADMIN 又需要 DBA
    @PreAuthorize("hasRole('ADMIN') and hasRole('DBA')")
    public String dba() {
        return "hello dba";
    }
}

基于数据库的认证

上述的认证数据定义在内存中,真实项目中,用户的基本信息和角色存储在数据库中。
数据库建表:

create table user (
    id int(11) primary key ,
    username varchar(32),
    password varchar(255),
    enabled tinyint(1),
    locked tinyint(1)
);

create table role(
    id int(11) primary key ,
    name varchar(32),
    nameZh varchar(32)
);

create table user_role(
    id int(11) primary key ,
    uid int(11),
    rid int(11)
);

默认插入一些数据:

use ay_user;show tables ;

insert into user (id, username, password, enabled, locked) values (1,'root','123','1','0');
insert into user (id, username, password, enabled, locked) values (2,'user','123','1','0');

insert into role (id, name, nameZh) values (1,'ROLE_dba','ADMINDBA');
insert into role (id, name, nameZh)  values (2,'ROLE_user','user');

insert into user_role values (1,'1','1');
insert into user_role values (2,'1','2');

创建实体类:

public class Role {
    private Integer id;
    private String name;
    private String nameZh;
    //省略getter/setter
    }

创建用户表类:

public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Boolean enabled;
    private Boolean locked;
    private List<Role> roles;
    // 获取当前用户的角色信息,角色都存储在roles,直接遍历roles 属性,然后构造集合并返回。
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return username;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return enabled;
    }
    //省略getter/setter

    

//    public Boolean getEnabled() {
//        return enabled;
//    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    public Boolean getLocked() {
        return locked;
    }

    public void setLocked(Boolean locked) {
        this.locked = locked;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
}

用户类需要实现UserDetails 接口,并实现接口的方法:
在这里插入图片描述
本案例中数据库只有enabled和locked 字段,故未过期和密码未过期都返回true.(不需要自己进行密码角色等匹配了)

创建UserService:

// 实现接口
@Service
public class UserService implements UserDetailsService {

    @Resource
    UserMapper userMapper;
// 用户登陆时的用户名,并通过用户名去数据库查找用户,如果没查到就抛出异常
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(username);
        if(user == null) {
            throw  new UsernameNotFoundException("账户不存在");
        }
        user.setRoles(userMapper.getUserRolesByUid(user.getId()));
        return user;
    }
}

UserMapper.java:

@Mapper
public interface UserMapper {
    User loadUserByUsername(String username);
    List<Role> getUserRolesByUid(Integer id);
}

其中UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.testspringboot.mapper.UserMapper">
    <select id="loadUserByUsername" resultType="com.example.testspringboot.model.User">
        select * from user where username=#{username}
    </select>
    <select id="getUserRolesByUid" resultType="com.example.testspringboot.model.Role">
        select * from role r,user_role ur where r.id=ur.rid and ur.uid=#{id}
    </select>
</mapper>

对Spring Security 配置:

@Configuration
public class SQLsecuity extends WebSecurityConfigurerAdapter {
    @Resource
    UserService userService;
    @Bean
    PasswordEncoder passwordEncoder() {
        return  NoOpPasswordEncoder.getInstance();
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/db/**").hasRole("dba")
                .antMatchers("/user/**").hasRole("user")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/login").permitAll()
                .and()
                .csrf().disable();
    }
}

这里同样没有使用加密方法,接着便可以像之前基于内存的方法在controller层进行测试了。

OAuth 2

OAuth 是一个开发标准,允许用户第三方应用访问在某一网站存储的私密资源,而这个过程中无须提供用户名和密码,实现这个功能是通过一个令牌(token)。例如,用户想通过QQ登录知乎,这是知乎就是第三方应用,知乎要访问用户的基本信息就需要得到授权,采取令牌的方式可以让用户灵活对第三方应用授权或者收回权限。传统Web开发基于Session,前后端分离的时候有不便,所以OAuth 2都可以解决。

OAuth 2授权模式

基本角色:资源所有者(用户)、客户端(上文提到的知乎)、授权服务器,资源服务器。
具体步骤:
在这里插入图片描述
一般来说授权模式有4种:
1.授权码模式 (基本都是使用这个) 2.简化模式 3.密码模式 4.客户端模式

实践

本次介绍的是在前后端分离应用提供的认证服务器如何搭建OAuth服务,主要是密码模式。

添加依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
<dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>

OAuth协议是在Spring Security 基础上完成的,因此要添加Spring Security 依赖,令牌可以存储在Redis缓存服务器上,同时Redis具有过期等功能,所以也加入Redis 依赖。

在application.properties配置Redis 连接信息:

# redis
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0

授权服务器和资源服务器可以是同一台服务器,通过不同的配置分别开启授权和资源服务器:
授权服务器:

@Configuration
@EnableAuthorizationServer
// 继承,完成对授权服务器配置,通过上面注解开启
public class AuthorizationServerConfig
        extends AuthorizationServerConfigurerAdapter {
        // 支持password 模式
    @Resource
    AuthenticationManager authenticationManager;
    // 用来完成Redis 缓存,将令牌信息存储到Redis 缓存中
    @Resource
    RedisConnectionFactory redisConnectionFactory;
    // 刷新token 提供支持
    @Autowired
    UserDetailsService userDetailsService;
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()
                .withClient("password")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(1800)
                .resourceIds("rid")
                .scopes("all")
                // 明文是123
                .secret("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq");
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security)
            throws Exception {
        security.allowFormAuthenticationForClients();
    }
}

资源服务器:

@Configuration
@EnableResourceServer
public class ResourceServerConfig
        extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(ResourceServerSecurityConfigurer resources)
            throws Exception {
            // 配置资源id,和授权服务器资源id 一致,然后设置这些资源仅基于令牌认证
        resources.resourceId("rid").stateless(true);
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/user/**").hasRole("user")
                .anyRequest().authenticated();
    }
}

配置Spring Security:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    // 注入授权服务器配置类使用
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return super.userDetailsService();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq")
                .roles("admin")
                .and()
                .withUser("sang")
                .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq")
                .roles("user");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/oauth/**").authorizeRequests()
        // 遇到oauth 直接放行
                .antMatchers("/oauth/**").permitAll()
                .and().csrf().disable();
    }
}

在controller层依旧是:

  @GetMapping("/admin/hello")
    public String admin() {
        return "hello admin!";
    }

    @GetMapping("/user/hello")
    public String user() {
        return "hello user";
    }



    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

现在启动Redis 服务器,首先发送一个POST请求,地址如下:

http://localhost:8080/oauth/token?username=sang&password=123&grant_type=password&client_id=password&scope=all&client_secret=123

在这里插入图片描述
请求地址包括了用户名密码授权模式 客户端id scope 以及客户端密码。
返回结果如上,其中access_token 是获取其他资源要用的令牌,refresh_token 用来刷新令牌,expires_in 表示过期时间,当过期后,使用refresh_token 重新获取新的access_token 。

访问所有资源,携带access_token 参数接口:
在这里插入图片描述
如果非法访问一个资源,访问/admin/hello:
在这里插入图片描述
接着来看Redis 数据:
在这里插入图片描述
至此一个password 模式的OAuth 认证体系就搭建完成了。
整体来说,Spring Security OAuth 2的使用还是比较复杂,配置也相当繁琐,如果应用场景较简单,可以按照上文搭建。

Spring Boot 整合 Shiro

Apache Shiro 是一个开源轻量级Java 安全框架,提供身份验证,授权,密码管理以及会话管理,相对于Spring Security ,Shiro 更加直观易用,也提供健壮的安全性。在SSM框架中,手动整合Shiro 配置步骤还是比较多,针对Spring Boot ,Shiro 提供了shiro-spring-boot-web-starter 用来简化配置。
引入依赖:

// 这部分代码可以不要,因为在shiro-spring已经集成了
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-web</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.5.3</version>
        </dependency>
<dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.1.0</version>
        </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

在application.properties 中配置Shiro 信息:

# 开启Shiro
shiro.enabled=true
# 开启Shiro Web
shiro.web.enabled=true
# 表示登录地址
shiro.loginUrl=/login
shiro.successUrl=/index
shiro.unauthorizedUrl=/unauthorized
# 表示允许通过URL 参数实现会话跟踪,如果网站支持Cook
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
shiro.sessionManager.sessionIdCookieEnabled=true

配置Shiro ,提供两个最基本的Bean:

@Configuration
public class ShiroConfig {

// 没有配置数据库连接,这里直接配置两个用户,分别对应不同的角色,同时角色也有不同的读写权限
    @Bean
    public Realm realm() {
        TextConfigurationRealm realm = new TextConfigurationRealm();
        realm.setUserDefinitions("sang=123,user\n admin=123,admin");
        realm.setRoleDefinitions("admin=read,write\n user=read");
        return realm;
    }
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition =
                new DefaultShiroFilterChainDefinition();
                // 可以匿名访问
        chainDefinition.addPathDefinition("/login", "anon");
        chainDefinition.addPathDefinition("/doLogin", "anon");
        // 注销操作
        chainDefinition.addPathDefinition("/logout", "logout");
        chainDefinition.addPathDefinition("/**", "authc");
        return chainDefinition;
    }
    // 如果不在Thymelead 使用Shiro,可以不写
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}

接下来配置登录接口以及页面访问接口:

@Controller
public class UserController {
    @PostMapping("/doLogin")
    public String doLogin(String username, String password, Model model) {
        System.out.println("123");
        UsernamePasswordToken token =
                new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            model.addAttribute("error", "用户名或密码输入错误!");
            return "login";
        }
        return "redirect:/index";
    }
    @RequiresRoles("admin")
    @GetMapping("/admin")
    public String admin() {看,
        return "admin";
    }
    @RequiresRoles(value = {"admin","user"},logical = Logical.OR)
    @GetMapping("/user")
    public String user() {
        return "user";
    }
}

在doLogin 中,构造了 UsernamePasswordToken实例,获取一个Subject对象并调用login 方法执行登录,当异常抛出的时候,说明登录失败,登录成功重新定向/index
对于其他不需要角色就能访问的接口,直接在WebMvc配置即可:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer{
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/index").setViewName("index");
        registry.addViewController("/unauthorized").setViewName("unauthorized");
    }
}

接下来创建全局异常处理器进行全局异常处理,本案例主要是处理授权异常:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer{
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/index").setViewName("index");
        registry.addViewController("/unauthorized").setViewName("unauthorized");
    }
}

然后分别建立5个html:
index.html:

<!DOCTYPE html>
<html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>Hello, <shiro:principal/></h3>
<h3><a href="/logout">注销登录</a></h3>
<h3><a shiro:hasRole="admin" href="/admin">管理员页面</a></h3>
<h3><a shiro:hasAnyRoles="admin,user" href="/user">普通用户页面</a></h3>
</body>
</html>

注意这里导入的名称空间和jsp中导入的shiro 是不同的。

login.html:

<form action="/doLogin" method="post">
        <input type="text" name="username"><br>
        <input type="password" name="password"><br>
        <div th:text="${error}"></div>
        <input type="submit" value="登录">
    </form>

user.html:

<h1>普通用户页面</h1>

admin.html:

<h1>管理员页面</h1>

unauthorized.html:

<div>
    <h3>未获授权,非法访问</h3>
    <h3 th:text="${error}"></h3>
</div>

下面开始测试,直接运行后,输入sang ,123 便可以登录:
在这里插入图片描述
如果此时的路径是admin,将会提示非法访问。
在这里插入图片描述

以上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/40169.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2. 如何给在 SAP Business Application Studio 里开发的 OData 服务准备测试数据

在开始本步骤的学习之前,请大家务必完成前一步骤1. SAP Business Application Studio 里创建一个基于 CAP 模型的最简单的 OData 服务的学习。换言之,大家已经在 SAP Business Technology Platform 上的 Business Application Studio 里,创建好了 Dev Space,并且拥有一个能…

JVM 别和我说你还不知道这几种垃圾回收器?Serial |Parallel|ParNew|CMS|G1|ZGC

Serial / Serial Old 从单词翻译过来看 serial 串行&#xff0c;每次它就是一款单线程收集器。 Serial 工作在新生代垃圾回收&#xff0c;Serial Old在老年代进行垃圾回收&#xff0c;Serial Old一般作为CMS 并发收集失败后的备选回收方案。 在垃圾收集器面前&#xff0c;它…

《Flowable流程引擎从零到壹》引入日志框架和部署流程定义

14天学习训练营导师课程&#xff1a; 邓澎波《Flowable流程引擎-基础篇【2022版】》 邓澎波《Flowable流程引擎-高级篇【2022版】》 学习笔记《Flowable流程引擎从零到壹》回城传送 ❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。Java领域优质创…

目标检测算法——YOLOv5/YOLOv7改进之结合​PP-LCNet(轻量级CPU网络)

>>>深度学习Tricks&#xff0c;第一时间送达<<< 目录 ​PP-LCNet——轻量级且超强悍的CPU级骨干网络&#xff01;&#xff01; &#xff08;一&#xff09;前沿介绍 1.PP-LCNet主要模块 2.相关实验结果 &#xff08;二&#xff09;YOLOv5/YOLOv7改进之结…

GreenPlum6.x之ETL工具

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、ETL是什么&#xff1f;二、数据加载工具GPLoad1.GPLoad安装部署2.编写控制文件test.yml总结前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#…

[Flask]Flask零基础项目---登录demo

借助Flask框架实现模拟用户登录操作&#xff1b; 一步一步的来实现这个登录接口 login.py from flask import Flask, render_template,requestapp Flask(__name__, template_folderlogin)app.route(/) def hello_flask():data request.get_data()print(data)return render_…

黑白图片和黑白图片上色系统毕业设计,AI黑背图像上色系统设计与实现,AI黑白照片上色系统论文毕设作品参考

功能清单 【后台管理员功能】 系统设置&#xff1a;设置网站简介、关于我们、联系我们、加入我们、法律声明 广告管理&#xff1a;设置小程序首页轮播图广告和链接 留言列表&#xff1a;所有用户留言信息列表&#xff0c;支持删除 会员列表&#xff1a;查看所有注册会员信息&a…

电子商务交易系统的设计与实现(javaee+mysql)

目录 1 概论 1 1.1电子商务交易发展 1 1.1.1电子商务交易 1 1.1.2发展前景&#xff1a; 1 2 系统可行性及需求分析 3 2.1 系统调研 3 2.2 系统可行性分析 3 2.2.1技术可行性分析 3 2.2.2 操作可行性分析 3 2.2.3 社会可行性分析 4 2.2.4可行性分析小结 4 2.3 系统需求分析 4 2.…

Windows10添加群晖磁盘映射,总是提示用户名密码不正确解决办法

在使用群晖NAS时&#xff0c;我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过winr键&#xff0c;输入\\NAS的IP地址&#xff0c;登录设备时总是提示”用户名或密码不正确”。但是实际密码是正确的。 原因描述&#xff1a;Windows 10&#xff08;或更早版本&a…

Android如何自定义服务器DynamicMockServer的使用

在平时开发时经常需要与服务器进行联调&#xff0c;但是服务器开发往往比前端的要滞后。这时候需要我们自己去mock数据来调通流程。 今天给大家介绍一款Android上的MockServer----DynamicMockServer&#xff0c;支持接口调用&#xff0c;静态文件。 DynamicMockServer&#x…

Web 安全:PKI 扫盲

个人博客 在互联网世界&#xff0c;我们广泛采用 TLS 来保护通信安全&#xff0c;这里的安全主要包含两部分内容&#xff1a;身份鉴别、通信加密。身份鉴别是一切的基础&#xff0c;特别当发送消息比较敏感需要加密时&#xff0c;对接收方必然有一个身份“假设”&#xff0c;“…

MacBook Pro M1 Docker 环境安装 Nacos 2.x 版本

MacBook Pro M1 Docker 环境安装 Nacos 2.x 版本 前言 由于 rocksdb 暂不支持 M1 平台&#xff0c;所以使用 Zulu JDK 的小伙伴们运行 Nacos 2.x 版本会报错&#xff0c;网上通用的解决方案是使用 Oracle JDK 来运行 Nacos 2.x 版本&#xff0c;但对于强迫症的我来说&#xf…

图书管理系(统附源码PPT)

图书管理系统1 绪 论1.1 研究背景1.2 研究意义1.3 相关研究现状1.3.1 高校图书管理面临的问题1.3.2 信息化为图书管理带来新变化2 相关技术2.1 JSP 概述2.2 MySQL 数据库技术2.3 Spring2.4 SpringMVC2.5 Dbcp2.6 Maven3 系统分析3.1 需求分析3.1.1 系统的功能需求分析3.1.2 系统…

【Git】一文带你入门Git分布式版本控制系统(必要配置、工作原理、创建/克隆项目)

Git 系列文章目录 Git 专栏参考链接Git&#xff08;一&#xff09;【Git】一文带你入门Git分布式版本控制系统&#xff08;简介&#xff0c;安装&#xff0c;Linux命令&#xff09;文章目录Git 系列文章目录一、Git 的必要配置二、Git 的工作原理三、Git 项目创建1、创建本地项…

【kafka】十四、kafka生产者API

kafka Producer API 1.消息发送流程 kafka的producer发送消息采用的是异步发送的方式。在消息的发送过程中&#xff0c;涉及到了两个线程–main线程和sender线程&#xff0c;以及一个线程共享变量–RecordAccumulator。main线程将消息发送给RecordAccumulator&#xff0c;send…

单向环形链表介绍以及约瑟夫问题分析

❤️一名热爱Java的大一学生&#xff0c;希望与各位大佬共同学习进步❤️ &#x1f9d1;个人主页&#xff1a;周小末天天开心 各位大佬的点赞&#x1f44d; 收藏⭐ 关注✅&#xff0c;是本人学习的最大动力 感谢&#xff01; &#x1f4d5;该篇文章收录专栏—数据结构 目录 单…

不敲代码就能搭建个人博客?快解析内网穿透来助力

记得很多年前看到一句话&#xff0c;“博客是一个人的狂欢”。无论是享受搭建的过程&#xff0c;还是享受创作的乐趣&#xff0c;更多时候博客是在取悦自己。那么&#xff0c;在2022年的今天&#xff0c;搭建个人博客还有意义吗&#xff1f;答案是肯定的&#xff0c;当我们在搜…

Day4: 应用篇-1

应用篇-1 环境安装 应用开发交叉编译环境&#xff0c; 【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.7.pdf 章节4.3.1 在 Ubuntu 中创建目录&#xff1a;/usr/local/arm&#xff0c;命令如下&#xff1a; sudo mkdir /usr/local/arm令将交叉编译器复制到/usr/local/arm 中…

Arduino开发实例-DIY分贝测量仪

DIY分贝测量仪 1、应用介绍 分贝计,它通常用于测量声音的强度和水平。 声音响度是用分贝来衡量的。 从飞机到人类耳语的不同发声介质都有一定的声音响度,以分贝表示。 声波是具有来回运动的纵波,给出高音或低音,如图所示: 声音的响度取决于频率或波长或传播所需的时间。…

APK构建过程-命令行编译

官方对APK构建过程的介绍 官方 - 构建流程介绍 典型 Android 应用模块的构建流程&#xff0c;按照以下常规步骤执行&#xff1a; 编译器将您的源代码转换成 DEX 文件&#xff08;Dalvik 可执行文件&#xff0c;其中包括在 Android 设备上运行的字节码&#xff09;&#xff0c;…