Spring Security
1 基本概念
1、两个重要的概念
- 认证:通过用户名和密码成功登陆系统后,让系统得到当前用户的角色身份。
- 授权:系统根据当前用户的角色,给其授予对应可以操作的权限资源。
2、完成权限管理需要的三个对象
- 用户:主要包含用户名、密码和当前用户的角色信息,可实现认证操作。
- 角色:主要包含角色名称、角色描述和当前角色拥有的权限信息,可实现授权操作。
- 权限:主要包含当前权限名称、URL地址等信息,可实现动态展示菜单。
- 三者之间的关系:用户↔角色↔权限。
3、Spring Security
- Spring Security是Spring采用AOP思想,基于Servlet过滤器实现的安全框架。它提供了完善的认证机制和方法级别的授权功能。
2 基本使用
1、创建项目
- 填写项目基本信息:
- 添加依赖:
- 项目存放路径:
添加druid依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency>
2、设计数据库表
user
- role
- user_role
3、实体类
User
package com.xianhuii.entity; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; import java.util.List; @Data public class User implements UserDetails { private Integer id; private String username; private String password; private Boolean enabled; private Boolean locked; private List<Role> 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; } }
Role
package com.xianhuii.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class Role { private Integer id; private String name; private String nameZh; }
4、DAO层
application.yaml
server: port: 8080 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://localhost:3306/spring-security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC username: root password: root mybatis: mapper-locations: classpath:/mapper/*.xml configuration: map-underscore-to-camel-case: true
UserMapper
package com.xianhuii.mapper; import com.xianhuii.entity.Role; import com.xianhuii.entity.User; import java.util.List; 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.xianhuii.mapper.UserMapper"> <!--User loadUserByUsername(String username)--> <select id="loadUserByUsername" resultType="com.xianhuii.entity.User"> select * from user where username = #{username} </select> <!--List<Role> getUserRolesByUid(Integer id)--> <select id="getUserRolesByUid" resultType="com.xianhuii.entity.Role"> select * from role r, user_role ur where r.id = ur.rid and ur.uid = #{id} </select> </mapper>
5、Service层
UserService
package com.xianhuii.service; import com.xianhuii.entity.User; import com.xianhuii.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; 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; @Service public class UserService implements UserDetailsService { @Autowired 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; } }
6、配置Spring Security
package com.xianhuii.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.xianhuii.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.*; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userService; // 配置密码加密规则 @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(10); } // 配置认证规则 @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("/user/**") .access("hasAnyRole('ADMIN', 'USER')") .antMatchers("/db/**") .access("hasRole('ADMIN') and hasRole('DBA')") .anyRequest() .authenticated() .and() .formLogin() .loginProcessingUrl("/login") .permitAll() .and() .formLogin() .loginPage("/login_page") .loginProcessingUrl("/login") .usernameParameter("name") .passwordParameter("passwd") .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 writer = response.getWriter(); response.setStatus(200); Map<String, Object> map = new HashMap<>(); map.put("status", 200); map.put("msg", principal); ObjectMapper om = new ObjectMapper(); writer.write(om.writeValueAsString(map)); writer.flush(); writer.close(); } }) .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { response.setContentType("application/json;charset=utf-8"); PrintWriter writer = response.getWriter(); response.setStatus(401); Map<String, Object> map = new HashMap<>(); map.put("status", 401); if (e instanceof LockedException) { map.put("msg", "账户被锁定,登录失败!"); } else if (e instanceof BadCredentialsException) { map.put("msg", "账户名或密码输入错误,登录失败!"); } else if (e instanceof DisabledException) { map.put("msg", "账户被禁用,登录失败!"); } else if (e instanceof AccountExpiredException) { map.put("msg", "账户已过期,登录失败!"); } else if (e instanceof CredentialsExpiredException) { map.put("msg", "密码已过期,登录失败!"); } else { map.put("msg", "登录失败!"); } ObjectMapper om = new ObjectMapper(); writer.write(om.writeValueAsString(map)); writer.flush(); writer.close(); } }) .permitAll() .and() .logout() .logoutUrl("/logout") .clearAuthentication(true) .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_page"); } }) .and() .csrf() .disable(); } }