我们在做一个系统的时候,最不可少的就是登陆,然而登陆就涉及到认证(校验账号和密码)与授权(权限)。在此做一个小小总结
一、基于session的认证与授权
项目目录:
我们先看简单的一些层:
controller
package com.secrity.hello.controller;
import com.secrity.hello.enity.AuthenticationRequest;
import com.secrity.hello.enity.User;
import com.secrity.hello.service.AuthenticationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
/**
* @ClassName LoginController
* @Description 验证用户登陆
* @Author
* @Date 2020/5/3 12:22
* @Version 1.0
**/
@RestController
public class LoginController {
@Autowired
private AuthenticationService authenticationService;
@PostMapping(value = "/login",produces = "text/plain;charset=utf-8")
public String login(AuthenticationRequest request, HttpSession httpSession){
User user = authenticationService.authentication(request);
httpSession.setAttribute(User.SESSION_USER_KEY,user);
return user.getNickname() + "登陆成功";
}
@GetMapping(value = "/loginout",produces = "text/plain;charset=utf-8")
public String loginOut(HttpSession httpSession){
httpSession.invalidate();
return "退出成功";
}
@GetMapping(value = "/resources/r1",produces = "text/plain;charset=utf-8")
public String res1(HttpSession session){
String nickname = null;
Object userObj = session.getAttribute(User.SESSION_USER_KEY);
if(userObj != null){
nickname =((User) userObj).getNickname();
}
return nickname +"访问资源r1";
}
@GetMapping(value = "/resources/r2",produces = "text/plain;charset=utf-8")
public String res2(HttpSession session){
String nickname = null;
Object userObj = session.getAttribute(User.SESSION_USER_KEY);
if(userObj != null){
nickname =((User) userObj).getNickname();
}
return nickname +"访问资源r2";
}
}
这里面一共有四个方法,第一个是登陆、第二个是退出。第三个和第四个是授权才能够访问的页面(拦截器拦截的页面)。这里的认证就是登陆,而授权就是看授予通过认证的用户是否有访问资源的权限。
service
package com.secrity.hello.service;
import com.secrity.hello.enity.AuthenticationRequest;
import com.secrity.hello.enity.User;
/**
* @ClassName AuthenticationService
* @Description 用户认证,服务接口
* @Author
* @Date 2020/5/3 12:11
* @Version 1.0
**/
public interface AuthenticationService {
/**
* 用户认证
* @param request 用户认证请求,账号和密码
* @return 认证成功的用户信息
*/
User authentication(AuthenticationRequest request);
}
package com.secrity.hello.service.Impl;
import com.secrity.hello.enity.AuthenticationRequest;
import com.secrity.hello.enity.User;
import com.secrity.hello.service.AuthenticationService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @ClassName AuthenticationServiceImpl
* @Description 用户认证,服务接口实现类
* @Author
* @Date 2020/5/3 12:21
* @Version 1.0
**/
@Service
public class AuthenticationServiceImpl implements AuthenticationService {
//模拟数据库用户信息
private Map<String,User> userMap = new HashMap<>();
/**
* 模拟查询数据库
*/
{
/**
* 权限:1001 =》 r1,1002 =》 r2
*/
Set<Integer> authorities1 = new HashSet<>();
authorities1.add(1001);
Set<Integer> authorities2 = new HashSet<>();
authorities2.add(1002);
userMap.put("zhangsan",new User(1l,"zhangsan","123","张三",authorities1));
userMap.put("lisi",new User(2l,"lisi","456","李四",authorities2));
userMap.put("wangwu",new User(3l,"wangwu","123","王五",null));
}
@Override
public User authentication(AuthenticationRequest request) {
if(request == null || StringUtils.isEmpty(request.getUsername()) || StringUtils.isEmpty(request.getPassword())){
throw new RuntimeException("账号和密码为空");
}
User user = selectUser(request.getUsername());
if(user == null){
throw new RuntimeException("查询不到该用户");
}
if(!request.getPassword().equals(user.getPassword())){
throw new RuntimeException("账号或密码错误");
}
return user;
}
public User selectUser(String username){
return userMap.get(username);
}
}
我们在这里没有调用数据库(我们的重点是明白认证与授权的过程)相信大家都能看得懂这个代码。
enity
package com.secrity.hello.enity;
import lombok.Data;
/**
* @ClassName AuthenticationRequest
* @Description 用户登陆请求类
* @Author
* @Date 2020/5/3 12:19
* @Version 1.0
**/
@Data
public class AuthenticationRequest {
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
前端请求的参数,我们封装成了一个实体类。
package com.secrity.hello.enity;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Set;
/**
* @ClassName User
* @Description 用户登陆实体类
* @Author
* @Date 2020/5/3 12:12
* @Version 1.0
**/
@Data
@AllArgsConstructor
public class User {
public static final String SESSION_USER_KEY = "_user";
/**
* 用户的信息
*/
private Long id;
private String username;
private String password;
private String nickname;
/**
* 权限
*/
private Set<Integer> authorities;
}
interceptor
package com.secrity.hello.interceptor;
import com.secrity.hello.enity.User;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @ClassName SimpleAuthenticationInterceptor
* @Description 拦截器
* @Author
* @Date 2020/5/3 12:58
* @Version 1.0
**/
@Component
public class SimpleAuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object object = request.getSession().getAttribute(User.SESSION_USER_KEY);
if(object == null){
//没有认证,提示登录
writeContent(response,"请登录");
}
User user = (User) object;
String requestURI = request.getRequestURI();
if( user.getAuthorities().contains(1001) && requestURI.contains("/resources/r1")){
return true;
}
if( user.getAuthorities().contains(1002) && requestURI.contains("/resources/r2")){
return true;
}
writeContent(response,"没有权限,拒绝访问");
return false;
}
private void writeContent(HttpServletResponse response, String msg) throws IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print(msg);
writer.close();
}
}
别跟我说你不知道拦截器,哈哈哈哈。拦截器体现在前端控制器的doDispach方法。有兴趣的可以看看源代码。preHandle是在访问某一个controller之前进行拦截执行。具体的代码的逻辑细节不难。
config
package com.secrity.hello.config;
import com.secrity.hello.interceptor.SimpleAuthenticationInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
* @ClassName WebMvcConfig
* @Description 相当于springmvc.xml文件
* @Author
* @Date 2020/5/3 12:56
* @Version 1.0
**/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.secrity.hello"
,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private SimpleAuthenticationInterceptor simpleAuthenticationInterceptor;
/**
* 视图解析器
*/
@Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
/**
* 配置自定义拦截器链
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(simpleAuthenticationInterceptor).addPathPatterns("/resources/**");
}
/**
* 配置路径与映射
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
}
}
这个配置类就相当于是相当于springmvc.xml文件。viewResolver方法是创建一个视图解析器。addInterceptors配置对应的资源路径的拦截。addViewControllers配置试图,就是你输入这个路径就访问到这个页面了。
package com.secrity.hello.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
/**
* @ClassName ApplicationConfig
* @Description 相当于applicationContext.xml文件,在此配置除了Controller的其它bean,比如:数据库链接池、事务管理器、业务bean等
* @Author
* @Date 2020/5/3 12:56
* @Version 1.0
**/
@Configuration
@ComponentScan(basePackages = "com.secrity.hello"
,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class ApplicationConfig {
}
相当于applicationContext.xml文件,在此配置除了Controller的其它bean,比如:数据库链接池、事务管理器、业务bean等
init
package com.secrity.hello.init;
import com.secrity.hello.config.ApplicationConfig;
import com.secrity.hello.config.WebMvcConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* @ClassName SpringApplicationInitializer
* @Description
* @Author
* @Date 2020/5/3 12:54
* @Version 1.0
**/
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//spring容器,相当于加载 applicationContext.xml
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{ApplicationConfig.class};
}
//servletContext,相当于加载springmvc.xml
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebMvcConfig.class};
}
//url-mapping
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
加载这些配置文件。相当于一个自定义的前端控制器。
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %>
<html>
<head>
<title>用户登录login</title>
</head>
<body>
用户登录login
<form action="login" method="post">
用户名:<input type="text" name="username"><br>
密 码:
<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
pom:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.secrity.hello</groupId>
<artifactId>Security</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>session-web</module>
</modules>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
</project>
二、基于security的认证与授权
这个要比以前老方法写的拦截器要简单一些
项目目录:
config
ApplicationConfig,这里跟之前上面的一样。我们看一下WebMvcConfig
package com.itheima.security.springmvc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
* @ClassName WebMvcConfig
* @Description 相当于SpringMvc.xml文件
* @Author
* @Date 2020/5/3 12:56
* @Version 1.0
**/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.itheima.security.springmvc"
,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebMvcConfig implements WebMvcConfigurer {
//视频解析器
@Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:/login");
}
}
这里面没有了添加拦截器的那个方法,原因是我们的security已经提供给我们了拦截器。这个跳转login页面跟之前也不一样,使用的是框架自带的登陆页面。
package com.itheima.security.springmvc.config;
import org.springframework.context.annotation.Bean;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
/**
* @ClassName ApplicationConfig
* @Description SpringSecurity的配置文件
* @Author
* @Date 2020/5/3 12:56
* @Version 1.0
**/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//定义用户信息服务(查询用户信息)
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("1001").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("1002").build());
return manager;
}
//密码编码器
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("1001")
.antMatchers("/r/r2").hasAuthority("1002")
.antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll()//除了/r/**,其它的请求可以访问
.and()
.formLogin()//允许表单登录
.successForwardUrl("/login-success");//自定义登录成功的页面地址
}
}
这个配置是我们的重点,userDetailsService是框架提供给我们的,我们这里使用的是框架带的类,并且也是在内存中的,如果查数据库是一样的道理。第二个方法是我们密码的一个编码加密校验方式,只要用户登陆就会帮我们去验证是否在这几个用户中,第三个方法是最重要的,配置了我们要对哪些url进行授权验证和认证成功之后跳转的登陆页面。
init
package com.itheima.security.springmvc.init;
import com.itheima.security.springmvc.config.ApplicationConfig;
import com.itheima.security.springmvc.config.WebMvcConfig;
import com.itheima.security.springmvc.config.WebSecurityConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* @ClassName SpringApplicationInitializer
* @Description 相当于前端控制器
* @Author
* @Date 2020/5/3 12:22
* @Version 1.0
**/
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//spring容器,相当于加载 applicationContext.xml
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{ApplicationConfig.class, WebSecurityConfig.class};
}
//servletContext,相当于加载springmvc.xml
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebMvcConfig.class};
}
//url-mapping
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
这里加了WebSecurityConfig的配置加载。
package com.itheima.security.springmvc.init;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/**
* @ClassName SpringSecurityApplicationInitializer
* @Description 初始化security的类,若当前环境没有spring和springMVC,则super(WebSecurityConfig.class)加上
* @Author
* @Date 2020/5/3 12:22
* @Version 1.0
**/
public class SpringSecurityApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SpringSecurityApplicationInitializer() {
//super(WebSecurityConfig.class);
}
}
这个配置是初始化Security的。
controller
package com.itheima.security.springmvc.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName LoginController
* @Description 验证用户登陆
* @Author
* @Date 2020/5/3 12:22
* @Version 1.0
**/
@RestController
public class LoginController {
@RequestMapping(value = "/login-success",produces = {"text/plain;charset=UTF-8"})
public String loginSuccess(){
return " 登录成功";
}
/**
* 测试资源1
* @return
*/
@GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF-8"})
public String r1(){
return " 访问资源1";
}
/**
* 测试资源2
* @return
*/
@GetMapping(value = "/r/r2",produces = {"text/plain;charset=UTF-8"})
public String r2(){
return " 访问资源2";
}
}
我们可以看到,用了Security框架之后我们的代码简化了很多。
pom:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.security.hello</groupId>
<artifactId>security-spring-security</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
<build>
<finalName>security-springmvc</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>utf-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>