<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itheima</groupId> <artifactId>chapter072</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> </parent> <dependencies> <!-- Spring Security提供的安全管理依赖启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- JDBC数据库连接启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- MySQL数据连接驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version><!--$NO-MVN-MAN-VER$ --> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Spring Data JPA操作数据库 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- Security与Thymeleaf整合实现前端页面安全访问控制 --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> </plugins> </build> </project>
server: port: 8082 spring: datasource: url: jdbc:mysql://localhost:3306/springbootdata?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8 username: root password: admin redis: host: 127.0.0.1 port: 6379 password:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>影视直播厅</title> </head> <body> <h1 align="center">欢迎进入电影网站首页</h1> <div sec:authorize="isAnonymous()"> <h2 align="center"> 游客您好,如果想查看电影<a th:href="@{/userLogin}">请登录</a> </h2> </div> <div sec:authorize="isAuthenticated()"> <h2 align="center"> <span sec:authentication="name" style="color: #007bff"></span>您好,您的用户权限为<span sec:authentication="principal.authorities" style="color:darkkhaki"></span>,您有权观看以下电影 </h2> <form th:action="@{/mylogout}" method="post"> <input th:type="submit" th:value="注销" /> </form> </div> <hr> <div sec:authorize="hasRole('common')"> <h3>普通电影</h3> <ul> <li><a th:href="@{/detail/common/1}">我不是药神</a></li> <li><a th:href="@{/detail/common/2}">夏洛特烦恼</a></li> </ul> </div> <div sec:authorize="hasAuthority('ROLE_vip')"> <h3>VIP专享</h3> <ul> <li><a th:href="@{/detail/vip/1}">速度与激情</a></li> <li><a th:href="@{/detail/vip/2}">猩球崛起</a></li> </ul> </div> </body> </html>
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>用户登录界面</title> <link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet"> <link th:href="@{/login/css/signin.css}" rel="stylesheet"> </head> <body class="text-center"> <form class="form-signin" th:action="@{/userLogin}" th:method="post"> <img class="mb-4" th:src="@{/login/img/login.jpg}" width="72px" height="72px"> <h1 class="h3 mb-3 font-weight-normal">请登录</h1> <!-- 用户登录错误信息提示框 --> <div th:if="${param.error}" style="color: red;height: 40px;text-align: left;font-size: 1.1em"> <img th:src="@{/login/img/loginError.jpg}" width="20px">用户名或密码错误,请重新登录! </div> <input type="text" name="name" class="form-control" placeholder="用户名" required="" autofocus=""> <input type="password" name="pwd" class="form-control" placeholder="密码" required=""> <div class="checkbox mb-3"> <label> <input type="checkbox" name="rememberme"> 记住我 </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">登录</button> <p class="mt-5 mb-3 text-muted">Copyright© 2019-2020</p> </form> </body> </html>
package com.itheima.domain; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity(name = "t_authority ") public class Authority implements Serializable{ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String authority; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getAuthority() { return authority; } public void setAuthority(String authority) { this.authority = authority; } @Override public String toString() { return "Authority{" + "id=" + id + ", authority='" + authority + '\'' + '}'; } } v
package com.itheima.domain; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity(name = "t_customer") public class Customer implements Serializable{ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String username; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "Customer{" + "id=" + id + ", username='" + username + '\'' + ", password=" + password + '}'; } }
package com.itheima.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import com.itheima.domain.Authority; public interface AuthorityRepository extends JpaRepository<Authority,Integer>{ @Query(value = "select a.* from t_customer c,t_authority a,t_customer_authority ca where ca.customer_id=c.id and ca.authority_id=a.id and c.username =?1",nativeQuery = true) public List<Authority> findAuthoritiesByUsername(String username); }
package com.itheima.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.itheima.domain.Customer; public interface CustomerRepository extends JpaRepository<Customer, Integer>{ Customer findByUsername(String username); }
package com.itheima.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import com.itheima.domain.Authority; import com.itheima.domain.Customer; import com.itheima.repository.AuthorityRepository; import com.itheima.repository.CustomerRepository; @Service public class CustomerService { @Autowired private CustomerRepository customerRepository; @Autowired private AuthorityRepository authorityRepository; @SuppressWarnings("rawtypes") @Autowired private RedisTemplate redisTemplate; // 业务控制:使用唯一用户名查询用户信息 @SuppressWarnings("unchecked") public Customer getCustomer(String username) { Customer customer = null; Object o = redisTemplate.opsForValue().get("customer_" + username); if (o != null) { customer = (Customer) o; } else { customer = customerRepository.findByUsername(username); if (customer != null) { redisTemplate.opsForValue().set("customer_" + username, customer); } } return customer; } // 业务控制:使用唯一用户名查询用户权限 @SuppressWarnings("unchecked") public List<Authority> getCustomerAuthority(String username) { List<Authority> authorities = null; Object o = redisTemplate.opsForValue().get("authorities_" + username); if (o != null) { authorities = (List<Authority>) o; } else { authorities = authorityRepository.findAuthoritiesByUsername(username); if (authorities.size() > 0) { redisTemplate.opsForValue().set("authorities_" + username, authorities); } } return authorities; } }
package com.itheima.service; import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; 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.Service; import com.itheima.domain.Authority; import com.itheima.domain.Customer; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private CustomerService customerService; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { // 通过业务方法获取用户及权限信息 Customer customer = customerService.getCustomer(s); List<Authority> authorities = customerService.getCustomerAuthority(s); // 对用户权限进行封装 List<SimpleGrantedAuthority> list = authorities.stream() .map(authority -> new SimpleGrantedAuthority(authority.getAuthority())).collect(Collectors.toList()); // 返回封装的UserDetails用户详情类 if (customer != null) { UserDetails userDetails = new User(customer.getUsername(), customer.getPassword(), list); return userDetails; } else { // 如果查询的用户不存在(用户名不存在),必须抛出此异常 throw new UsernameNotFoundException("当前用户不存在!"); } } }
package com.itheima.config; import java.time.Duration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; @Configuration public class RedisConfig { /** * 定制Redis API模板RedisTemplate * * @param redisConnectionFactory * @return */ @Bean public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>(); template.setConnectionFactory(redisConnectionFactory); // 使用JSON格式序列化对象,对缓存数据key和value进行转换 Jackson2JsonRedisSerializer<Object> jacksonSeial = new Jackson2JsonRedisSerializer<Object>(Object.class); // 解决查询缓存转换异常的问题 ObjectMapper om = new ObjectMapper(); // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(om); // 设置RedisTemplate模板API的序列化方式为JSON template.setDefaultSerializer(jacksonSeial); return template; } /** * 定制Redis缓存管理器RedisCacheManager,实现自定义序列化并设置缓存时效 * * @param redisConnectionFactory * @return */ @Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { // 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换 RedisSerializer<String> strSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer<Object> jacksonSeial = new Jackson2JsonRedisSerializer<Object>(Object.class); // 解决查询缓存转换异常的问题 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(om); // 定制缓存数据序列化方式及时效 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(1)) // 设置缓存有效期为1天 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(strSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSeial)) .disableCachingNullValues(); // 对空数据不进行缓存 RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config) .build(); return cacheManager; } }
package com.itheima.config; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; 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.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import com.itheima.service.UserDetailsServiceImpl; @EnableWebSecurity // 开启MVC security安全支持 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @Autowired private UserDetailsServiceImpl userDetailsService; /** * 用户授权管理自定义配置 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { // 自定义用户授权管理 http.authorizeRequests().antMatchers("/").permitAll() // 需要对static文件夹下静态资源进行统一放行 .antMatchers("/login/**").permitAll().antMatchers("/detail/common/**").hasRole("common") .antMatchers("/detail/vip/**").hasRole("vip").anyRequest().authenticated(); // 自定义用户登录控制 http.formLogin().loginPage("/userLogin").permitAll().usernameParameter("name").passwordParameter("pwd") .defaultSuccessUrl("/").failureUrl("/userLogin?error"); // 自定义用户退出控制 http.logout().logoutUrl("/mylogout").logoutSuccessUrl("/"); // 定制Remember-me记住我功能 http.rememberMe().rememberMeParameter("rememberme").tokenValiditySeconds(200) // 对cookie信息进行持久化管理 .tokenRepository(tokenRepository()); // 可以关闭Spring Security默认开启的CSRF防护功能 // http.csrf().disable(); } /** * 持久化Token存储 * * @return */ @Bean public JdbcTokenRepositoryImpl tokenRepository() { JdbcTokenRepositoryImpl jr = new JdbcTokenRepositoryImpl(); jr.setDataSource(dataSource); return jr; } /** * 用户身份认证自定义配置 * * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 密码需要设置编码器 BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); // // 1、使用内存用户信息,作为测试使用 // auth.inMemoryAuthentication().passwordEncoder(encoder) // .withUser("shitou").password(encoder.encode("123456")).roles("common") // .and() // .withUser("李四").password(encoder.encode("123456")).roles("vip"); // // 2、使用JDBC进行身份认证 // String userSQL ="select username,password,valid from t_customer " + // "where username = ?"; // String authoritySQL="select c.username,a.authority from t_customer c,t_authority a,"+ // "t_customer_authority ca where ca.customer_id=c.id " + // "and ca.authority_id=a.id and c.username =?"; // auth.jdbcAuthentication().passwordEncoder(encoder) // .dataSource(dataSource) // .usersByUsernameQuery(userSQL) // .authoritiesByUsernameQuery(authoritySQL); // 3、使用UserDetailsService进行身份认证 auth.userDetailsService(userDetailsService).passwordEncoder(encoder); } }
package com.itheima.controller; import java.util.Enumeration; import javax.servlet.http.HttpSession; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class FilmeController { // 影片详情页 @GetMapping("/detail/{type}/{path}") public String toDetail(@PathVariable("type") String type, @PathVariable("path") String path) { return "detail/" + type + "/" + path; } // 向用户登录页面跳转 @GetMapping("/userLogin") public String toLoginPage() { return "login/login"; } /** * 通过传统的HttpSession获取Security控制的登录用户信息 * * @param session */ @GetMapping("/getuserBySession") @ResponseBody public void getUser(HttpSession session) { // 从当前HttpSession获取绑定到此会话的所有对象的名称 Enumeration<String> names = session.getAttributeNames(); while (names.hasMoreElements()) { // 获取HttpSession中会话名称 String element = names.nextElement(); // 获取HttpSession中的应用上下文 SecurityContextImpl attribute = (SecurityContextImpl) session.getAttribute(element); System.out.println("element: " + element); System.out.println("attribute: " + attribute); // 获取用户相关信息 Authentication authentication = attribute.getAuthentication(); UserDetails principal = (UserDetails) authentication.getPrincipal(); System.out.println(principal); System.out.println("username: " + principal.getUsername()); } } /** * 通过Security提供的SecurityContextHolder获取登录用户信息 */ @GetMapping("/getuserByContext") @ResponseBody public void getUser2() { // 获取应用上下文 SecurityContext context = SecurityContextHolder.getContext(); System.out.println("userDetails: " + context); // 获取用户相关信息 Authentication authentication = context.getAuthentication(); UserDetails principal = (UserDetails) authentication.getPrincipal(); System.out.println(principal); System.out.println("username: " + principal.getUsername()); } }
package com.itheima.controller; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class CSRFController { // 向用户修改页跳转 @GetMapping("/toUpdate") public String toUpdate() { return "csrf/csrfTest"; } // 用户修改提交处理 @ResponseBody @PostMapping(value = "/updateUser") public String updateUser(@RequestParam String username, @RequestParam String password, HttpServletRequest request) { System.out.println(username); System.out.println(password); String csrf_token = request.getParameter("_csrf"); System.out.println(csrf_token); return "ok"; } }
package com.itheima; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义